This commit is contained in:
39
Cargo.lock
generated
39
Cargo.lock
generated
@@ -2,6 +2,15 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.98"
|
||||
@@ -43,6 +52,7 @@ dependencies = [
|
||||
"itertools",
|
||||
"pad",
|
||||
"rand",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_yml",
|
||||
"thiserror 1.0.69",
|
||||
@@ -390,6 +400,35 @@ dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.7"
|
||||
|
||||
@@ -11,10 +11,11 @@ thiserror = "1"
|
||||
rand = "0.8"
|
||||
graphviz-rust = "0.9.4"
|
||||
itertools = "0.14.0"
|
||||
regex = "1.11.1"
|
||||
|
||||
[dependencies.uuid]
|
||||
version = "1.17.0"
|
||||
# Lets you generate random UUIDs
|
||||
features = [
|
||||
"v4",
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::configotron::fsm::TransitionMask;
|
||||
use crate::configotron::fsm::{masks::decompose_transition, FiniteStateMachine, TransitionMask};
|
||||
use graphviz_rust::dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, Node, NodeId, Stmt, Vertex};
|
||||
use uuid::Uuid;
|
||||
|
||||
|
||||
|
||||
@@ -71,9 +73,70 @@ pub fn mask_to_string(mask: TransitionMask) -> String {
|
||||
|
||||
}
|
||||
|
||||
pub fn create_graphviz_graph(fsm: &FiniteStateMachine) -> Graph {
|
||||
let mut stmts: Vec<Stmt> = Vec::new();
|
||||
|
||||
let mut unamed_state_count: u32 = 0;
|
||||
let mut states_sorted: Vec<_> = fsm.states.iter().collect();
|
||||
states_sorted.sort_by(|a, b| a.1.name.cmp(&b.1.name));
|
||||
|
||||
fn format_id(id: &Uuid) -> Id {
|
||||
Id::Plain(format!("\"{}\"", id.to_string()))
|
||||
}
|
||||
|
||||
for (k, state) in states_sorted {
|
||||
// Adding all nodes
|
||||
let label = if state.name.len() == 0 {
|
||||
format!("z{}", unamed_state_count)
|
||||
} else {
|
||||
state.name.clone()
|
||||
};
|
||||
|
||||
unamed_state_count += 1;
|
||||
|
||||
let shape = match state.state_type {
|
||||
super::StateType::Terminal => "doublecircle".to_string(),
|
||||
super::StateType::Error => "circle".to_string(),
|
||||
super::StateType::None => "circle".to_string(),
|
||||
};
|
||||
|
||||
let color = match state.state_type {
|
||||
super::StateType::Terminal => "black".to_string(),
|
||||
super::StateType::Error => "red".to_string(),
|
||||
super::StateType::None => "black".to_string(),
|
||||
};
|
||||
|
||||
stmts.push(Stmt::Node(Node {
|
||||
id: NodeId(format_id(k), None),
|
||||
attributes: vec![
|
||||
Attribute(Id::Plain("label".to_string()), Id::Plain(label)),
|
||||
Attribute(Id::Plain("shape".to_string()), Id::Plain(shape)),
|
||||
Attribute(Id::Plain("color".to_string()), Id::Plain(color))
|
||||
]
|
||||
}));
|
||||
|
||||
// Adding all edges
|
||||
|
||||
for ((state_ref, action), mask) in decompose_transition(&state.transition) {
|
||||
stmts.push(Stmt::Edge(Edge {
|
||||
ty: EdgeTy::Pair(
|
||||
Vertex::N(NodeId(format_id(k), None)),
|
||||
Vertex::N(NodeId(format_id(&state_ref), None))
|
||||
),
|
||||
attributes: vec![
|
||||
Attribute(Id::Plain("label".to_string()), Id::Plain(format!("\"{}\\n/{}/\"", mask_to_string(mask), action)))
|
||||
]
|
||||
}));
|
||||
}
|
||||
}
|
||||
Graph::DiGraph { id: Id::Plain("FSM".to_string()), strict: true, stmts: stmts }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::configotron::fsm::{display::mask_to_string, masks::{mask_all, mask_char, mask_char_range}};
|
||||
use graphviz_rust::{cmd::{CommandArg, Format}, exec, 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};
|
||||
|
||||
#[test]
|
||||
fn mask_to_string_test() {
|
||||
@@ -111,4 +174,43 @@ mod test {
|
||||
"'a','c','e'..'g','i','k'..'m','o','q','s','u','w','y','z'".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_graph_dot_lang() {
|
||||
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);
|
||||
|
||||
// Transitions:
|
||||
// z0: 'a' -> z1, 'b' -> z2
|
||||
// z1: 'a' -> z1, 'b' -> z2
|
||||
// z2: 'a' -> z2
|
||||
// z3: * -> z3
|
||||
// Error/Default: z3
|
||||
|
||||
fsm_builder.add_link(&z0, &z1, Action::None, mask_char('a')).unwrap();
|
||||
fsm_builder.add_link(&z0, &z2, Action::None, mask_char('b')).unwrap();
|
||||
|
||||
fsm_builder.add_link(&z1, &z1, Action::None, mask_char('a')).unwrap();
|
||||
fsm_builder.add_link(&z1, &z2, Action::None, mask_char('b')).unwrap();
|
||||
|
||||
fsm_builder.add_link(&z2, &z2, Action::None, mask_char('a')).unwrap();
|
||||
|
||||
let res_fsm = fsm_builder.finish(&z3, Action::None, &z0);
|
||||
assert!(res_fsm.is_ok());
|
||||
if let Ok(fsm) = res_fsm {
|
||||
let graph = create_graphviz_graph(&fsm);
|
||||
|
||||
let string = graph.print(&mut PrinterContext::default());
|
||||
println!("Original: {}", string);
|
||||
|
||||
let data = exec(graph, &mut PrinterContext::default(), vec![
|
||||
CommandArg::Format(Format::Svg)
|
||||
]).unwrap();
|
||||
|
||||
std::fs::write("/tmp/create_graph_dot_lang.svg", &data).expect("Failed to write SVG file");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ 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)> {
|
||||
pub fn decompose_transition<T: Hash + Eq + Clone>(array: &TransitionArray<T>) -> Vec<(T, TransitionMask)> {
|
||||
let mut results: HashMap<T, TransitionMask> = HashMap::new();
|
||||
|
||||
for (pos, i) in array.iter().enumerate() {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
pub mod display;
|
||||
pub mod masks;
|
||||
|
||||
use std::{collections::{HashMap, HashSet}};
|
||||
use std::{collections::{HashMap, HashSet}, fmt};
|
||||
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -9,12 +10,12 @@ pub type StateRef = Uuid;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum StateType {
|
||||
EndState,
|
||||
ErrorState,
|
||||
Terminal,
|
||||
Error,
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum Action {
|
||||
None,
|
||||
Push,
|
||||
@@ -22,6 +23,17 @@ pub enum Action {
|
||||
Submit
|
||||
}
|
||||
|
||||
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::Pop => write!(f, "Pop"),
|
||||
Action::Submit => write!(f, "Submit"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type TransitionArray<T> = [T; 128];
|
||||
pub type UnfinishedTransitionArray<T> = [Option<T>; 128];
|
||||
|
||||
@@ -162,12 +174,12 @@ mod test {
|
||||
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::EndState);
|
||||
let z3 = fsm_builder.add_state("z3".to_string(), StateType::ErrorState);
|
||||
let z2 = fsm_builder.add_state("z2".to_string(), StateType::Terminal);
|
||||
let z3 = fsm_builder.add_state("z3".to_string(), StateType::Error);
|
||||
|
||||
// These are unsed states and should be eliminated
|
||||
fsm_builder.add_state("u4".to_string(), StateType::ErrorState);
|
||||
let u5 =fsm_builder.add_state("u5".to_string(), StateType::ErrorState);
|
||||
fsm_builder.add_state("u4".to_string(), StateType::Error);
|
||||
let u5 =fsm_builder.add_state("u5".to_string(), StateType::Error);
|
||||
|
||||
fsm_builder.add_link(&u5, &u5, Action::None, mask_all()).unwrap();
|
||||
|
||||
@@ -199,22 +211,22 @@ mod test {
|
||||
|
||||
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, &"b".to_string()), StateType::Terminal);
|
||||
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, &"ab".to_string()), StateType::Terminal);
|
||||
assert_eq!(run_fsm(&fsm, &"ba".to_string()), StateType::Terminal); // 'b' leads to z2, then 'a' stays in z2
|
||||
assert_eq!(run_fsm(&fsm, &"bb".to_string()), StateType::Error);
|
||||
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);
|
||||
assert_eq!(run_fsm(&fsm, &"aab".to_string()), StateType::Terminal);
|
||||
assert_eq!(run_fsm(&fsm, &"abb".to_string()), StateType::Error);
|
||||
assert_eq!(run_fsm(&fsm, &"abc".to_string()), StateType::Error); // 'c' is not defined, should go to default z3
|
||||
assert_eq!(run_fsm(&fsm, &"c".to_string()), StateType::Error);
|
||||
assert_eq!(run_fsm(&fsm, &"cab".to_string()), StateType::Error);
|
||||
assert_eq!(run_fsm(&fsm, &"aabc".to_string()), StateType::Error);
|
||||
assert_eq!(run_fsm(&fsm, &"bca".to_string()), StateType::Error);
|
||||
assert_eq!(run_fsm(&fsm, &"aaaaab".to_string()), StateType::Terminal);
|
||||
assert_eq!(run_fsm(&fsm, &"aaaaac".to_string()), StateType::Error);
|
||||
assert_eq!(run_fsm(&fsm, &"bbbb".to_string()), StateType::Error);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user