This commit is contained in:
39
Cargo.lock
generated
39
Cargo.lock
generated
@@ -2,6 +2,15 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
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]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.98"
|
version = "1.0.98"
|
||||||
@@ -43,6 +52,7 @@ dependencies = [
|
|||||||
"itertools",
|
"itertools",
|
||||||
"pad",
|
"pad",
|
||||||
"rand",
|
"rand",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_yml",
|
"serde_yml",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
@@ -390,6 +400,35 @@ dependencies = [
|
|||||||
"getrandom 0.2.16",
|
"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]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ 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"
|
||||||
|
regex = "1.11.1"
|
||||||
|
|
||||||
[dependencies.uuid]
|
[dependencies.uuid]
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
# Lets you generate random UUIDs
|
# Lets you generate random UUIDs
|
||||||
features = [
|
features = [
|
||||||
"v4",
|
"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)]
|
#[cfg(test)]
|
||||||
mod 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]
|
#[test]
|
||||||
fn mask_to_string_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()
|
"'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};
|
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();
|
let mut results: HashMap<T, TransitionMask> = HashMap::new();
|
||||||
|
|
||||||
for (pos, i) in array.iter().enumerate() {
|
for (pos, i) in array.iter().enumerate() {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod masks;
|
pub mod masks;
|
||||||
|
|
||||||
use std::{collections::{HashMap, HashSet}};
|
use std::{collections::{HashMap, HashSet}, fmt};
|
||||||
|
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -9,12 +10,12 @@ pub type StateRef = Uuid;
|
|||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub enum StateType {
|
pub enum StateType {
|
||||||
EndState,
|
Terminal,
|
||||||
ErrorState,
|
Error,
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
None,
|
None,
|
||||||
Push,
|
Push,
|
||||||
@@ -22,6 +23,17 @@ pub enum Action {
|
|||||||
Submit
|
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 TransitionArray<T> = [T; 128];
|
||||||
pub type UnfinishedTransitionArray<T> = [Option<T>; 128];
|
pub type UnfinishedTransitionArray<T> = [Option<T>; 128];
|
||||||
|
|
||||||
@@ -162,12 +174,12 @@ mod test {
|
|||||||
let mut fsm_builder = FSMBuilder::new();
|
let mut fsm_builder = FSMBuilder::new();
|
||||||
let z0 = fsm_builder.add_state("z0".to_string(), StateType::None);
|
let z0 = fsm_builder.add_state("z0".to_string(), StateType::None);
|
||||||
let z1 = fsm_builder.add_state("z1".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 z2 = fsm_builder.add_state("z2".to_string(), StateType::Terminal);
|
||||||
let z3 = fsm_builder.add_state("z3".to_string(), StateType::ErrorState);
|
let z3 = fsm_builder.add_state("z3".to_string(), StateType::Error);
|
||||||
|
|
||||||
// These are unsed states and should be eliminated
|
// These are unsed states and should be eliminated
|
||||||
fsm_builder.add_state("u4".to_string(), StateType::ErrorState);
|
fsm_builder.add_state("u4".to_string(), StateType::Error);
|
||||||
let u5 =fsm_builder.add_state("u5".to_string(), StateType::ErrorState);
|
let u5 =fsm_builder.add_state("u5".to_string(), StateType::Error);
|
||||||
|
|
||||||
fsm_builder.add_link(&u5, &u5, Action::None, mask_all()).unwrap();
|
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, &"".to_string()), StateType::None);
|
||||||
assert_eq!(run_fsm(&fsm, &"a".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, &"aa".to_string()), StateType::None);
|
||||||
assert_eq!(run_fsm(&fsm, &"ab".to_string()), StateType::EndState);
|
assert_eq!(run_fsm(&fsm, &"ab".to_string()), StateType::Terminal);
|
||||||
assert_eq!(run_fsm(&fsm, &"ba".to_string()), StateType::EndState); // 'b' leads to z2, then 'a' stays in z2
|
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::ErrorState);
|
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, &"aaa".to_string()), StateType::None);
|
||||||
assert_eq!(run_fsm(&fsm, &"aab".to_string()), StateType::EndState);
|
assert_eq!(run_fsm(&fsm, &"aab".to_string()), StateType::Terminal);
|
||||||
assert_eq!(run_fsm(&fsm, &"abb".to_string()), StateType::ErrorState);
|
assert_eq!(run_fsm(&fsm, &"abb".to_string()), StateType::Error);
|
||||||
assert_eq!(run_fsm(&fsm, &"abc".to_string()), StateType::ErrorState); // 'c' is not defined, should go to default z3
|
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::ErrorState);
|
assert_eq!(run_fsm(&fsm, &"c".to_string()), StateType::Error);
|
||||||
assert_eq!(run_fsm(&fsm, &"cab".to_string()), StateType::ErrorState);
|
assert_eq!(run_fsm(&fsm, &"cab".to_string()), StateType::Error);
|
||||||
assert_eq!(run_fsm(&fsm, &"aabc".to_string()), StateType::ErrorState);
|
assert_eq!(run_fsm(&fsm, &"aabc".to_string()), StateType::Error);
|
||||||
assert_eq!(run_fsm(&fsm, &"bca".to_string()), StateType::ErrorState);
|
assert_eq!(run_fsm(&fsm, &"bca".to_string()), StateType::Error);
|
||||||
assert_eq!(run_fsm(&fsm, &"aaaaab".to_string()), StateType::EndState);
|
assert_eq!(run_fsm(&fsm, &"aaaaab".to_string()), StateType::Terminal);
|
||||||
assert_eq!(run_fsm(&fsm, &"aaaaac".to_string()), StateType::ErrorState);
|
assert_eq!(run_fsm(&fsm, &"aaaaac".to_string()), StateType::Error);
|
||||||
assert_eq!(run_fsm(&fsm, &"bbbb".to_string()), StateType::ErrorState);
|
assert_eq!(run_fsm(&fsm, &"bbbb".to_string()), StateType::Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user