seperated fsm into 3 files
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -1,502 +0,0 @@
|
|||||||
use graphviz_rust::{
|
|
||||||
dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, NodeId, Stmt, Vertex},
|
|
||||||
exec,
|
|
||||||
printer::PrinterContext,
|
|
||||||
};
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FSM {
|
|
||||||
name: String,
|
|
||||||
states: Vec<Rc<RefCell<FSMState>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FSM {
|
|
||||||
pub fn new(state: Vec<Rc<RefCell<FSMState>>>, name: &str) -> Self {
|
|
||||||
FSM {
|
|
||||||
states: state,
|
|
||||||
name: name.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_stmts(&self) -> Vec<Stmt> {
|
|
||||||
let mut stmts: Vec<Stmt> = vec![Stmt::Attribute(Attribute(
|
|
||||||
Id::Plain("rankdir".to_string()),
|
|
||||||
Id::Plain("LR".to_string()),
|
|
||||||
))];
|
|
||||||
for s in &self.states {
|
|
||||||
let s_borrowed = s.borrow();
|
|
||||||
|
|
||||||
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()),
|
|
||||||
)];
|
|
||||||
match s_borrowed.action {
|
|
||||||
FSMStateAction::NONE => {
|
|
||||||
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
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
match &s_borrowed.transition {
|
|
||||||
None => {}
|
|
||||||
Some(t) => {
|
|
||||||
for (t, range) in t.to_targets_vec() {
|
|
||||||
match t.upgrade() {
|
|
||||||
None => {}
|
|
||||||
Some(target) => {
|
|
||||||
stmts.push(Stmt::Edge(Edge {
|
|
||||||
ty: EdgeTy::Pair(
|
|
||||||
Vertex::N(NodeId(
|
|
||||||
Id::Plain(s_borrowed.to_graph_label()),
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
Vertex::N(NodeId(
|
|
||||||
Id::Plain(target.borrow().to_graph_label()),
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
attributes: vec![Attribute(
|
|
||||||
Id::Plain("label".to_string()),
|
|
||||||
Id::Plain(format!("\"{}\"", range.to_string())),
|
|
||||||
)],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stmts
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_graph(&self, format: graphviz_rust::cmd::Format) -> Result<Vec<u8>, std::io::Error> {
|
|
||||||
let g = Graph::DiGraph {
|
|
||||||
id: Id::Plain("root".to_string()),
|
|
||||||
strict: false,
|
|
||||||
stmts: self.render_stmts(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ctx: graphviz_rust::printer::PrinterContext = PrinterContext::default();
|
|
||||||
|
|
||||||
println!("{}", graphviz_rust::print(g.clone(), &mut ctx));
|
|
||||||
|
|
||||||
let data = exec(g, &mut ctx, vec![format.into()])?;
|
|
||||||
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum FSMStateAction {
|
|
||||||
NONE,
|
|
||||||
TERMINAL,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FSMState {
|
|
||||||
name: String,
|
|
||||||
action: FSMStateAction,
|
|
||||||
transition: Option<tranisitions::FSMTranistion>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FSMState {
|
|
||||||
pub fn new(name: String, action: FSMStateAction) -> Self {
|
|
||||||
FSMState {
|
|
||||||
name: name,
|
|
||||||
action: action,
|
|
||||||
transition: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_transitions(&mut self, t: tranisitions::FSMTranistion) {
|
|
||||||
self.transition = Some(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_graph_label(&self) -> String {
|
|
||||||
return format!("\"{}\"", self.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_graph_hash(&self) -> String {
|
|
||||||
self.to_graph_label()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod tranisitions {
|
|
||||||
use super::transition_masking::TransitionMask;
|
|
||||||
use super::FSMState;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::{cell::RefCell, rc::Weak};
|
|
||||||
|
|
||||||
type StateRef = Weak<RefCell<FSMState>>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FSMTranistion {
|
|
||||||
array: [StateRef; 128],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FSMTranistion {
|
|
||||||
pub fn new_from_mask(mask: TransitionMask, pos: StateRef, default: StateRef) -> Self {
|
|
||||||
let mut array: [StateRef; 128] = std::array::from_fn(|_| Weak::clone(&default));
|
|
||||||
for i in 0..128 {
|
|
||||||
if mask.get_bit(i) {
|
|
||||||
array[i] = Weak::clone(&pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FSMTranistion { array }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(default: StateRef) -> Self {
|
|
||||||
let array: [StateRef; 128] = std::array::from_fn(|_| Weak::clone(&default));
|
|
||||||
|
|
||||||
FSMTranistion { array }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply(&mut self, mask: TransitionMask, state: StateRef) {
|
|
||||||
for i in 0..128 {
|
|
||||||
if mask.get_bit(i) {
|
|
||||||
self.array[i] = Weak::clone(&state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_targets_vec(&self) -> Vec<(StateRef, TransitionMask)> {
|
|
||||||
let mut target_maps: HashMap<String, (StateRef, TransitionMask)> = HashMap::new();
|
|
||||||
|
|
||||||
self.array.iter().enumerate().for_each(|(index, target)| {
|
|
||||||
match target.upgrade() {
|
|
||||||
None => {}
|
|
||||||
Some(t) => {
|
|
||||||
let s_borrowd = t.borrow();
|
|
||||||
let key = s_borrowd.to_graph_label();
|
|
||||||
match target_maps.get_mut(&key) {
|
|
||||||
Some((_, mask)) => {
|
|
||||||
mask.set_bit(index);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let mut mask = TransitionMask::new();
|
|
||||||
mask.set_bit(index);
|
|
||||||
target_maps.insert(key, (Rc::downgrade(&t), mask));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut res: Vec<(StateRef, TransitionMask)> = Vec::new();
|
|
||||||
|
|
||||||
target_maps
|
|
||||||
.iter()
|
|
||||||
.for_each(|(_, (target, mask))| match target.upgrade() {
|
|
||||||
Some(t) => {
|
|
||||||
res.push((Rc::downgrade(&t), mask.clone()));
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
});
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod transition_masking {
|
|
||||||
use itertools::Itertools;
|
|
||||||
use std::{ops::RangeInclusive, usize};
|
|
||||||
|
|
||||||
pub enum MaskPresets {
|
|
||||||
Char(char),
|
|
||||||
AnyCase,
|
|
||||||
LowerCase,
|
|
||||||
UpperCase,
|
|
||||||
AllHumanReadable,
|
|
||||||
Numbers,
|
|
||||||
SpecialChars,
|
|
||||||
WhiteSpace,
|
|
||||||
LineTermination,
|
|
||||||
NoChar,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TransitionMask {
|
|
||||||
mask: u128,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MaskPresets> for TransitionMask {
|
|
||||||
fn from(value: MaskPresets) -> Self {
|
|
||||||
let mut n: u128 = 0;
|
|
||||||
|
|
||||||
let mut set_range = |r: RangeInclusive<i32>| {
|
|
||||||
r.for_each(|i| n |= 1 << i);
|
|
||||||
};
|
|
||||||
|
|
||||||
match value {
|
|
||||||
MaskPresets::NoChar => {}
|
|
||||||
MaskPresets::AnyCase => {
|
|
||||||
set_range(0x41..=0x5a); // UpperCase
|
|
||||||
set_range(0x61..=0x7a); // LowerCase
|
|
||||||
}
|
|
||||||
MaskPresets::LowerCase => {
|
|
||||||
set_range(0x61..=0x7a); // LowerCase
|
|
||||||
}
|
|
||||||
MaskPresets::UpperCase => {
|
|
||||||
set_range(0x41..=0x5a); // UpperCase
|
|
||||||
}
|
|
||||||
MaskPresets::AllHumanReadable => {
|
|
||||||
set_range(0x20..=0x7e); // All Ascii Chars + Space + Tab
|
|
||||||
n |= 1 << 0x9; // Tab
|
|
||||||
}
|
|
||||||
MaskPresets::Numbers => {
|
|
||||||
set_range(0x30..=0x39); // Numbers
|
|
||||||
}
|
|
||||||
MaskPresets::SpecialChars => {
|
|
||||||
set_range(0x21..=0x2F); // ! to /
|
|
||||||
set_range(0x3A..=0x40); // : to @
|
|
||||||
set_range(0x5B..=0x0D); // [ to `
|
|
||||||
set_range(0x7B..=0x7E); // { to ~
|
|
||||||
}
|
|
||||||
MaskPresets::WhiteSpace => {
|
|
||||||
n |= 1 << 0x9; // Tab
|
|
||||||
n |= 1 << 0x20; // Spaces
|
|
||||||
}
|
|
||||||
MaskPresets::LineTermination => {
|
|
||||||
n |= 1 << 0x0a; // Line Feed
|
|
||||||
n |= 1 << 0x0d; // Carriage Return
|
|
||||||
}
|
|
||||||
MaskPresets::Char(c) => {
|
|
||||||
if c.is_ascii() {
|
|
||||||
n |= 1 << (c as u32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TransitionMask { mask: n }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransitionMask {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
TransitionMask { mask: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn or(&self, other: Self) -> TransitionMask {
|
|
||||||
TransitionMask {
|
|
||||||
mask: other.mask | self.mask,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn and(&self, other: Self) -> TransitionMask {
|
|
||||||
TransitionMask {
|
|
||||||
mask: other.mask & self.mask,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inv(&self) -> TransitionMask {
|
|
||||||
TransitionMask { mask: !self.mask }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_bit(&self, i: usize) -> bool {
|
|
||||||
(self.mask & (1 << i)) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_bit(&mut self, i: usize) {
|
|
||||||
self.mask |= 1 << i;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_string(&self) -> String {
|
|
||||||
let mut res: Vec<String> = Vec::new();
|
|
||||||
let mut strike: Option<usize> = None;
|
|
||||||
|
|
||||||
fn num_to_str(i: usize) -> String {
|
|
||||||
if (0x21..=0x7E).contains(&i) {
|
|
||||||
format!("'{}'", ((i as u8) as char).to_string())
|
|
||||||
} else {
|
|
||||||
match i {
|
|
||||||
0x20 => "SP".to_string(),
|
|
||||||
0x0D => "LF".to_string(),
|
|
||||||
0x0A => "CR".to_string(),
|
|
||||||
0x09 => "TAB".to_string(),
|
|
||||||
_ => format!("{:02x}", i),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (p, n) in (0..128).tuple_windows() {
|
|
||||||
match (strike, self.get_bit(p), self.get_bit(n)) {
|
|
||||||
(None, true, true) => {
|
|
||||||
// First two
|
|
||||||
strike = Some(p);
|
|
||||||
}
|
|
||||||
(None, true, false) => {
|
|
||||||
// First, only one
|
|
||||||
res.push(num_to_str(p));
|
|
||||||
}
|
|
||||||
(None, false, true) => {
|
|
||||||
// Strik Start
|
|
||||||
strike = Some(n)
|
|
||||||
}
|
|
||||||
(None, false, false) => {
|
|
||||||
// No Strike
|
|
||||||
}
|
|
||||||
(Some(_), true, true) => {
|
|
||||||
// Stike ongoing
|
|
||||||
}
|
|
||||||
(Some(s), true, false) => {
|
|
||||||
// Strik end
|
|
||||||
if s == p {
|
|
||||||
// Single Number
|
|
||||||
res.push(num_to_str(p));
|
|
||||||
} else {
|
|
||||||
// Range
|
|
||||||
res.push(format!("{}-{}", num_to_str(s), num_to_str(p)));
|
|
||||||
}
|
|
||||||
|
|
||||||
strike = None;
|
|
||||||
}
|
|
||||||
(Some(_), false, true) => {
|
|
||||||
// This should no happend
|
|
||||||
strike = Some(n);
|
|
||||||
}
|
|
||||||
(Some(_), false, false) => {
|
|
||||||
// Should also no happend
|
|
||||||
strike = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match strike {
|
|
||||||
None => (),
|
|
||||||
Some(s) => {
|
|
||||||
if s == 127 {
|
|
||||||
res.push(num_to_str(s));
|
|
||||||
} else {
|
|
||||||
res.push(format!("{}-{}", num_to_str(s), num_to_str(127)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
res.join(",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use graphviz_rust::cmd::Format;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use super::{tranisitions::*, transition_masking::*, *};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn transition_mask_to_string() {
|
|
||||||
let mut mask1 = TransitionMask::new();
|
|
||||||
|
|
||||||
mask1.set_bit(0);
|
|
||||||
|
|
||||||
(10..=16).for_each(|i| {
|
|
||||||
mask1.set_bit(i);
|
|
||||||
});
|
|
||||||
|
|
||||||
(50..=51).for_each(|i| {
|
|
||||||
mask1.set_bit(i);
|
|
||||||
});
|
|
||||||
|
|
||||||
mask1.set_bit(60);
|
|
||||||
mask1.set_bit(62);
|
|
||||||
mask1.set_bit(64);
|
|
||||||
|
|
||||||
(70..=73).for_each(|i| {
|
|
||||||
mask1.set_bit(i);
|
|
||||||
});
|
|
||||||
|
|
||||||
(126..=127).for_each(|i| {
|
|
||||||
mask1.set_bit(i);
|
|
||||||
});
|
|
||||||
|
|
||||||
println!("{}", mask1.to_string());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
mask1.to_string(),
|
|
||||||
"00,CR-10,'2'-'3','<','>','@','F'-'I','~'-7f"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_fsm_render() {
|
|
||||||
let z0 = Rc::new(RefCell::new(FSMState::new(
|
|
||||||
"z0".to_string(),
|
|
||||||
FSMStateAction::NONE,
|
|
||||||
)));
|
|
||||||
let z1 = Rc::new(RefCell::new(FSMState::new(
|
|
||||||
"z1".to_string(),
|
|
||||||
FSMStateAction::NONE,
|
|
||||||
)));
|
|
||||||
let z2 = Rc::new(RefCell::new(FSMState::new(
|
|
||||||
"z2".to_string(),
|
|
||||||
FSMStateAction::TERMINAL,
|
|
||||||
)));
|
|
||||||
let z3 = Rc::new(RefCell::new(FSMState::new(
|
|
||||||
"z3".to_string(),
|
|
||||||
FSMStateAction::NONE,
|
|
||||||
)));
|
|
||||||
|
|
||||||
let mut z0_transisions = FSMTranistion::new_from_mask(
|
|
||||||
TransitionMask::from(MaskPresets::Char('a')),
|
|
||||||
Rc::downgrade(&z1),
|
|
||||||
Rc::downgrade(&z3),
|
|
||||||
);
|
|
||||||
z0_transisions.apply(
|
|
||||||
TransitionMask::from(MaskPresets::Char('b')),
|
|
||||||
Rc::downgrade(&z2),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut z1_transisions = FSMTranistion::new_from_mask(
|
|
||||||
TransitionMask::from(MaskPresets::Char('a')),
|
|
||||||
Rc::downgrade(&z1),
|
|
||||||
Rc::downgrade(&z3),
|
|
||||||
);
|
|
||||||
z1_transisions.apply(
|
|
||||||
TransitionMask::from(MaskPresets::Char('b')),
|
|
||||||
Rc::downgrade(&z2),
|
|
||||||
);
|
|
||||||
|
|
||||||
let z2_transisions = FSMTranistion::new_from_mask(
|
|
||||||
TransitionMask::from(MaskPresets::Char('a')),
|
|
||||||
Rc::downgrade(&z2),
|
|
||||||
Rc::downgrade(&z3),
|
|
||||||
);
|
|
||||||
|
|
||||||
let z3_transisions = FSMTranistion::new(Rc::downgrade(&z3));
|
|
||||||
|
|
||||||
z0.borrow_mut().set_transitions(z0_transisions);
|
|
||||||
z1.borrow_mut().set_transitions(z1_transisions);
|
|
||||||
z2.borrow_mut().set_transitions(z2_transisions);
|
|
||||||
z3.borrow_mut().set_transitions(z3_transisions);
|
|
||||||
|
|
||||||
let fsm = FSM::new(vec![z0, z1, z2, z3], "Test FSM #1");
|
|
||||||
|
|
||||||
let data = fsm.render_graph(Format::Svg).unwrap();
|
|
||||||
|
|
||||||
let mut output_file = std::fs::File::create("/tmp/debug-simle_fsm_building.svg").unwrap();
|
|
||||||
output_file.write_all(&data).unwrap();
|
|
||||||
|
|
||||||
|
|
||||||
let svg_content = std::fs::read_to_string("/tmp/debug-simle_fsm_building.svg").unwrap();
|
|
||||||
assert_eq!(svg_content, String::from_utf8(data).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
218
src/configotron/fsm/masking.rs
Normal file
218
src/configotron/fsm/masking.rs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use std::{ops::RangeInclusive, usize};
|
||||||
|
|
||||||
|
pub enum MaskPresets {
|
||||||
|
Char(char),
|
||||||
|
AnyCase,
|
||||||
|
LowerCase,
|
||||||
|
UpperCase,
|
||||||
|
AllHumanReadable,
|
||||||
|
Numbers,
|
||||||
|
SpecialChars,
|
||||||
|
WhiteSpace,
|
||||||
|
LineTermination,
|
||||||
|
NoChar,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TransitionMask {
|
||||||
|
mask: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MaskPresets> for TransitionMask {
|
||||||
|
fn from(value: MaskPresets) -> Self {
|
||||||
|
let mut n: u128 = 0;
|
||||||
|
|
||||||
|
let mut set_range = |r: RangeInclusive<i32>| {
|
||||||
|
r.for_each(|i| n |= 1 << i);
|
||||||
|
};
|
||||||
|
|
||||||
|
match value {
|
||||||
|
MaskPresets::NoChar => {}
|
||||||
|
MaskPresets::AnyCase => {
|
||||||
|
set_range(0x41..=0x5a); // UpperCase
|
||||||
|
set_range(0x61..=0x7a); // LowerCase
|
||||||
|
}
|
||||||
|
MaskPresets::LowerCase => {
|
||||||
|
set_range(0x61..=0x7a); // LowerCase
|
||||||
|
}
|
||||||
|
MaskPresets::UpperCase => {
|
||||||
|
set_range(0x41..=0x5a); // UpperCase
|
||||||
|
}
|
||||||
|
MaskPresets::AllHumanReadable => {
|
||||||
|
set_range(0x20..=0x7e); // All Ascii Chars + Space + Tab
|
||||||
|
n |= 1 << 0x9; // Tab
|
||||||
|
}
|
||||||
|
MaskPresets::Numbers => {
|
||||||
|
set_range(0x30..=0x39); // Numbers
|
||||||
|
}
|
||||||
|
MaskPresets::SpecialChars => {
|
||||||
|
set_range(0x21..=0x2F); // ! to /
|
||||||
|
set_range(0x3A..=0x40); // : to @
|
||||||
|
set_range(0x5B..=0x0D); // [ to `
|
||||||
|
set_range(0x7B..=0x7E); // { to ~
|
||||||
|
}
|
||||||
|
MaskPresets::WhiteSpace => {
|
||||||
|
n |= 1 << 0x9; // Tab
|
||||||
|
n |= 1 << 0x20; // Spaces
|
||||||
|
}
|
||||||
|
MaskPresets::LineTermination => {
|
||||||
|
n |= 1 << 0x0a; // Line Feed
|
||||||
|
n |= 1 << 0x0d; // Carriage Return
|
||||||
|
}
|
||||||
|
MaskPresets::Char(c) => {
|
||||||
|
if c.is_ascii() {
|
||||||
|
n |= 1 << (c as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TransitionMask { mask: n }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransitionMask {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
TransitionMask { mask: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn or(&self, other: Self) -> TransitionMask {
|
||||||
|
TransitionMask {
|
||||||
|
mask: other.mask | self.mask,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn and(&self, other: Self) -> TransitionMask {
|
||||||
|
TransitionMask {
|
||||||
|
mask: other.mask & self.mask,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inv(&self) -> TransitionMask {
|
||||||
|
TransitionMask { mask: !self.mask }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bit(&self, i: usize) -> bool {
|
||||||
|
(self.mask & (1 << i)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_bit(&mut self, i: usize) {
|
||||||
|
self.mask |= 1 << i;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
let mut res: Vec<String> = Vec::new();
|
||||||
|
let mut strike: Option<usize> = None;
|
||||||
|
|
||||||
|
fn num_to_str(i: usize) -> String {
|
||||||
|
if (0x21..=0x7E).contains(&i) {
|
||||||
|
format!("'{}'", ((i as u8) as char).to_string())
|
||||||
|
} else {
|
||||||
|
match i {
|
||||||
|
0x20 => "SP".to_string(),
|
||||||
|
0x0D => "LF".to_string(),
|
||||||
|
0x0A => "CR".to_string(),
|
||||||
|
0x09 => "TAB".to_string(),
|
||||||
|
_ => format!("{:02x}", i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (p, n) in (0..128).tuple_windows() {
|
||||||
|
match (strike, self.get_bit(p), self.get_bit(n)) {
|
||||||
|
(None, true, true) => {
|
||||||
|
// First two
|
||||||
|
strike = Some(p);
|
||||||
|
}
|
||||||
|
(None, true, false) => {
|
||||||
|
// First, only one
|
||||||
|
res.push(num_to_str(p));
|
||||||
|
}
|
||||||
|
(None, false, true) => {
|
||||||
|
// Strik Start
|
||||||
|
strike = Some(n)
|
||||||
|
}
|
||||||
|
(None, false, false) => {
|
||||||
|
// No Strike
|
||||||
|
}
|
||||||
|
(Some(_), true, true) => {
|
||||||
|
// Stike ongoing
|
||||||
|
}
|
||||||
|
(Some(s), true, false) => {
|
||||||
|
// Strik end
|
||||||
|
if s == p {
|
||||||
|
// Single Number
|
||||||
|
res.push(num_to_str(p));
|
||||||
|
} else {
|
||||||
|
// Range
|
||||||
|
res.push(format!("{}-{}", num_to_str(s), num_to_str(p)));
|
||||||
|
}
|
||||||
|
|
||||||
|
strike = None;
|
||||||
|
}
|
||||||
|
(Some(_), false, true) => {
|
||||||
|
// This should no happend
|
||||||
|
strike = Some(n);
|
||||||
|
}
|
||||||
|
(Some(_), false, false) => {
|
||||||
|
// Should also no happend
|
||||||
|
strike = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match strike {
|
||||||
|
None => (),
|
||||||
|
Some(s) => {
|
||||||
|
if s == 127 {
|
||||||
|
res.push(num_to_str(s));
|
||||||
|
} else {
|
||||||
|
res.push(format!("{}-{}", num_to_str(s), num_to_str(127)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
res.join(",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mod test {
|
||||||
|
use super::TransitionMask;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transition_mask_to_string() {
|
||||||
|
let mut mask1 = TransitionMask::new();
|
||||||
|
|
||||||
|
mask1.set_bit(0);
|
||||||
|
|
||||||
|
(10..=16).for_each(|i| {
|
||||||
|
mask1.set_bit(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
(50..=51).for_each(|i| {
|
||||||
|
mask1.set_bit(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
mask1.set_bit(60);
|
||||||
|
mask1.set_bit(62);
|
||||||
|
mask1.set_bit(64);
|
||||||
|
|
||||||
|
(70..=73).for_each(|i| {
|
||||||
|
mask1.set_bit(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
(126..=127).for_each(|i| {
|
||||||
|
mask1.set_bit(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("{}", mask1.to_string());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
mask1.to_string(),
|
||||||
|
"00,CR-10,'2'-'3','<','>','@','F'-'I','~'-7f"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
216
src/configotron/fsm/mod.rs
Normal file
216
src/configotron/fsm/mod.rs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
pub mod masking;
|
||||||
|
pub mod transition;
|
||||||
|
|
||||||
|
use graphviz_rust::{
|
||||||
|
dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, NodeId, Stmt, Vertex},
|
||||||
|
exec,
|
||||||
|
printer::PrinterContext,
|
||||||
|
};
|
||||||
|
use transition::FSMTranistion;
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FSM {
|
||||||
|
name: String,
|
||||||
|
states: Vec<Rc<RefCell<FSMState>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FSM {
|
||||||
|
pub fn new(state: Vec<Rc<RefCell<FSMState>>>, name: &str) -> Self {
|
||||||
|
FSM {
|
||||||
|
states: state,
|
||||||
|
name: name.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_stmts(&self) -> Vec<Stmt> {
|
||||||
|
let mut stmts: Vec<Stmt> = vec![Stmt::Attribute(Attribute(
|
||||||
|
Id::Plain("rankdir".to_string()),
|
||||||
|
Id::Plain("LR".to_string()),
|
||||||
|
))];
|
||||||
|
for s in &self.states {
|
||||||
|
let s_borrowed = s.borrow();
|
||||||
|
|
||||||
|
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()),
|
||||||
|
)];
|
||||||
|
match s_borrowed.action {
|
||||||
|
FSMStateAction::NONE => {
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
match &s_borrowed.transition {
|
||||||
|
None => {}
|
||||||
|
Some(t) => {
|
||||||
|
for (t, range) in t.to_targets_vec() {
|
||||||
|
match t.upgrade() {
|
||||||
|
None => {}
|
||||||
|
Some(target) => {
|
||||||
|
stmts.push(Stmt::Edge(Edge {
|
||||||
|
ty: EdgeTy::Pair(
|
||||||
|
Vertex::N(NodeId(
|
||||||
|
Id::Plain(s_borrowed.to_graph_label()),
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
Vertex::N(NodeId(
|
||||||
|
Id::Plain(target.borrow().to_graph_label()),
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
attributes: vec![Attribute(
|
||||||
|
Id::Plain("label".to_string()),
|
||||||
|
Id::Plain(format!("\"{}\"", range.to_string())),
|
||||||
|
)],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stmts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_graph(&self, format: graphviz_rust::cmd::Format) -> Result<Vec<u8>, std::io::Error> {
|
||||||
|
let g = Graph::DiGraph {
|
||||||
|
id: Id::Plain("root".to_string()),
|
||||||
|
strict: false,
|
||||||
|
stmts: self.render_stmts(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ctx: graphviz_rust::printer::PrinterContext = PrinterContext::default();
|
||||||
|
|
||||||
|
println!("{}", graphviz_rust::print(g.clone(), &mut ctx));
|
||||||
|
|
||||||
|
let data = exec(g, &mut ctx, vec![format.into()])?;
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum FSMStateAction {
|
||||||
|
NONE,
|
||||||
|
TERMINAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FSMState {
|
||||||
|
name: String,
|
||||||
|
action: FSMStateAction,
|
||||||
|
transition: Option<FSMTranistion>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FSMState {
|
||||||
|
pub fn new(name: String, action: FSMStateAction) -> Self {
|
||||||
|
FSMState {
|
||||||
|
name: name,
|
||||||
|
action: action,
|
||||||
|
transition: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_transitions(&mut self, t: FSMTranistion) {
|
||||||
|
self.transition = Some(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_graph_label(&self) -> String {
|
||||||
|
return format!("\"{}\"", self.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_graph_hash(&self) -> String {
|
||||||
|
self.to_graph_label()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use graphviz_rust::cmd::Format;
|
||||||
|
use std::{cell::RefCell, io::Write, rc::Rc};
|
||||||
|
use super::{FSMState, FSMStateAction, FSM};
|
||||||
|
use super::transition::FSMTranistion;
|
||||||
|
use super::masking::{MaskPresets, TransitionMask};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_fsm_render() {
|
||||||
|
let z0 = Rc::new(RefCell::new(FSMState::new(
|
||||||
|
"z0".to_string(),
|
||||||
|
FSMStateAction::NONE,
|
||||||
|
)));
|
||||||
|
let z1 = Rc::new(RefCell::new(FSMState::new(
|
||||||
|
"z1".to_string(),
|
||||||
|
FSMStateAction::NONE,
|
||||||
|
)));
|
||||||
|
let z2 = Rc::new(RefCell::new(FSMState::new(
|
||||||
|
"z2".to_string(),
|
||||||
|
FSMStateAction::TERMINAL,
|
||||||
|
)));
|
||||||
|
let z3 = Rc::new(RefCell::new(FSMState::new(
|
||||||
|
"z3".to_string(),
|
||||||
|
FSMStateAction::NONE,
|
||||||
|
)));
|
||||||
|
|
||||||
|
let mut z0_transisions = FSMTranistion::new_from_mask(
|
||||||
|
TransitionMask::from(MaskPresets::Char('a')),
|
||||||
|
Rc::downgrade(&z1),
|
||||||
|
Rc::downgrade(&z3),
|
||||||
|
);
|
||||||
|
z0_transisions.apply(
|
||||||
|
TransitionMask::from(MaskPresets::Char('b')),
|
||||||
|
Rc::downgrade(&z2),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut z1_transisions = FSMTranistion::new_from_mask(
|
||||||
|
TransitionMask::from(MaskPresets::Char('a')),
|
||||||
|
Rc::downgrade(&z1),
|
||||||
|
Rc::downgrade(&z3),
|
||||||
|
);
|
||||||
|
z1_transisions.apply(
|
||||||
|
TransitionMask::from(MaskPresets::Char('b')),
|
||||||
|
Rc::downgrade(&z2),
|
||||||
|
);
|
||||||
|
|
||||||
|
let z2_transisions = FSMTranistion::new_from_mask(
|
||||||
|
TransitionMask::from(MaskPresets::Char('a')),
|
||||||
|
Rc::downgrade(&z2),
|
||||||
|
Rc::downgrade(&z3),
|
||||||
|
);
|
||||||
|
|
||||||
|
let z3_transisions = FSMTranistion::new(Rc::downgrade(&z3));
|
||||||
|
|
||||||
|
z0.borrow_mut().set_transitions(z0_transisions);
|
||||||
|
z1.borrow_mut().set_transitions(z1_transisions);
|
||||||
|
z2.borrow_mut().set_transitions(z2_transisions);
|
||||||
|
z3.borrow_mut().set_transitions(z3_transisions);
|
||||||
|
|
||||||
|
let fsm = FSM::new(vec![z0, z1, z2, z3], "Test FSM #1");
|
||||||
|
|
||||||
|
let data = fsm.render_graph(Format::Svg).unwrap();
|
||||||
|
|
||||||
|
let mut output_file = std::fs::File::create("/tmp/debug-simle_fsm_building.svg").unwrap();
|
||||||
|
output_file.write_all(&data).unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
let svg_content = std::fs::read_to_string("/tmp/debug-simle_fsm_building.svg").unwrap();
|
||||||
|
assert_eq!(svg_content, String::from_utf8(data).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/configotron/fsm/transition.rs
Normal file
76
src/configotron/fsm/transition.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
|
||||||
|
use super::masking::TransitionMask;
|
||||||
|
use super::FSMState;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::{cell::RefCell, rc::Weak};
|
||||||
|
|
||||||
|
type StateRef = Weak<RefCell<FSMState>>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FSMTranistion {
|
||||||
|
array: [StateRef; 128],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FSMTranistion {
|
||||||
|
pub fn new_from_mask(mask: TransitionMask, pos: StateRef, default: StateRef) -> Self {
|
||||||
|
let mut array: [StateRef; 128] = std::array::from_fn(|_| Weak::clone(&default));
|
||||||
|
for i in 0..128 {
|
||||||
|
if mask.get_bit(i) {
|
||||||
|
array[i] = Weak::clone(&pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FSMTranistion { array }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(default: StateRef) -> Self {
|
||||||
|
let array: [StateRef; 128] = std::array::from_fn(|_| Weak::clone(&default));
|
||||||
|
|
||||||
|
FSMTranistion { array }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(&mut self, mask: TransitionMask, state: StateRef) {
|
||||||
|
for i in 0..128 {
|
||||||
|
if mask.get_bit(i) {
|
||||||
|
self.array[i] = Weak::clone(&state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_targets_vec(&self) -> Vec<(StateRef, TransitionMask)> {
|
||||||
|
let mut target_maps: HashMap<String, (StateRef, TransitionMask)> = HashMap::new();
|
||||||
|
|
||||||
|
self.array.iter().enumerate().for_each(|(index, target)| {
|
||||||
|
match target.upgrade() {
|
||||||
|
None => {}
|
||||||
|
Some(t) => {
|
||||||
|
let s_borrowd = t.borrow();
|
||||||
|
let key = s_borrowd.to_graph_label();
|
||||||
|
match target_maps.get_mut(&key) {
|
||||||
|
Some((_, mask)) => {
|
||||||
|
mask.set_bit(index);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut mask = TransitionMask::new();
|
||||||
|
mask.set_bit(index);
|
||||||
|
target_maps.insert(key, (Rc::downgrade(&t), mask));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut res: Vec<(StateRef, TransitionMask)> = Vec::new();
|
||||||
|
|
||||||
|
target_maps
|
||||||
|
.iter()
|
||||||
|
.for_each(|(_, (target, mask))| match target.upgrade() {
|
||||||
|
Some(t) => {
|
||||||
|
res.push((Rc::downgrade(&t), mask.clone()));
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
});
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user