Added FSM rendering via graphviz

This commit is contained in:
AlexanderHD27
2025-05-31 20:56:43 +02:00
parent e8b0811a9e
commit 2d762db3bb
9 changed files with 829 additions and 331 deletions

16
Cargo.lock generated
View File

@@ -34,6 +34,7 @@ name = "configotron"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"graphviz-rust", "graphviz-rust",
"itertools",
"pad", "pad",
"rand", "rand",
"serde", "serde",
@@ -85,6 +86,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498cfcded997a93eb31edd639361fa33fd229a8784e953b37d71035fe3890b7b" checksum = "498cfcded997a93eb31edd639361fa33fd229a8784e953b37d71035fe3890b7b"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -194,6 +201,15 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.15"

View File

@@ -10,3 +10,4 @@ serde_yml = "0.0.12"
thiserror = "1" thiserror = "1"
rand = "0.8" rand = "0.8"
graphviz-rust = "0.9.4" graphviz-rust = "0.9.4"
itertools = "0.14.0"

Binary file not shown.

View File

@@ -1,35 +1,33 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fs}; use std::fs;
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
pub enum UserConfigItem { pub enum UserConfigItem {
String { String {
name: String, name: String,
max_length: u64 max_length: u64,
}, },
IPv4 { IPv4 {
name: String, name: String,
}, },
Boolean { Boolean {
name: String name: String,
}, },
Number { Number {
name: String, name: String,
max: Option<i32>, max: Option<i32>,
min: Option<i32> min: Option<i32>,
}, },
Submenu(UserConfigData) Submenu(UserConfigData),
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
pub struct UserConfigData { pub struct UserConfigData {
pub name: String, pub name: String,
pub config_items: Vec<UserConfigItem> pub config_items: Vec<UserConfigItem>,
} }
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum ConfigotronError { pub enum ConfigotronError {
#[error("IO Operation failed: {}", ._0.to_string())] #[error("IO Operation failed: {}", ._0.to_string())]
@@ -39,16 +37,16 @@ pub enum ConfigotronError {
YamlParsingError(#[from] serde_yml::Error), YamlParsingError(#[from] serde_yml::Error),
#[error("Missing field {} (line {} col {}) {}", .missing_filed, .line, .col, .context)] #[error("Missing field {} (line {} col {}) {}", .missing_filed, .line, .col, .context)]
MissingFieldError{ MissingFieldError {
col: i32, col: i32,
line: i32, line: i32,
missing_filed: Box<str>, missing_filed: Box<str>,
context: Box<str> context: Box<str>,
} },
} }
pub fn read_and_parse_yaml_file(input_file: &str) -> Result<UserConfigData, ConfigotronError> { pub fn read_and_parse_yaml_file(input_file: &str) -> Result<UserConfigData, ConfigotronError> {
let file_content = fs::read_to_string(&input_file)?; let file_content = fs::read_to_string(&input_file)?;
let config: UserConfigData = serde_yml::from_str(&file_content)?; let config: UserConfigData = serde_yml::from_str(&file_content)?;
return Ok(config); return Ok(config);
} }

View File

@@ -1,15 +1,15 @@
use std::{fmt::format, usize};
use pad::PadStr; use pad::PadStr;
use std::usize;
use crate::configotron::{conf::{UserConfigData, UserConfigItem}, doc::grid}; use crate::configotron::conf::{UserConfigData, UserConfigItem};
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum DocGridCellType{ pub enum DocGridCellType {
Boarder, Boarder,
Empty, Empty,
SubMenu { text: Vec<String> }, SubMenu { text: Vec<String> },
Item { text: Vec<String> }, Item { text: Vec<String> },
ItemJoin ItemJoin,
} }
impl DocGridCellType { impl DocGridCellType {
@@ -19,44 +19,51 @@ impl DocGridCellType {
DocGridCellType::Item { .. } => "I".to_string(), DocGridCellType::Item { .. } => "I".to_string(),
DocGridCellType::ItemJoin => "i".to_string(), DocGridCellType::ItemJoin => "i".to_string(),
DocGridCellType::SubMenu { .. } => "S".to_string(), DocGridCellType::SubMenu { .. } => "S".to_string(),
DocGridCellType::Boarder => "B".to_string() DocGridCellType::Boarder => "B".to_string(),
} }
} }
} }
pub enum ResultingGridCell { pub enum ResultingGridCell {
Grid(DocGrid), Grid(DocGrid),
Cell(DocGridCellType) Cell(DocGridCellType),
} }
fn split_string(s: &String) -> Vec<String> { fn split_string(s: &String) -> Vec<String> {
s.split('\n').map(|s| s.to_string()).collect::<Vec<String>>() s.split('\n')
} .map(|s| s.to_string())
.collect::<Vec<String>>()
}
fn user_config_item_to_grid_cells(item: &UserConfigItem) -> ResultingGridCell { fn user_config_item_to_grid_cells(item: &UserConfigItem) -> ResultingGridCell {
match item { match item {
UserConfigItem::String { name, max_length } => { UserConfigItem::String { name, max_length } => {
ResultingGridCell::Cell(DocGridCellType::Item { text: vec![ ResultingGridCell::Cell(DocGridCellType::Item {
name.to_string(), format!("[length: 0..{}]", max_length) text: vec![name.to_string(), format!("[length: 0..{}]", max_length)],
]} })
)}, }
UserConfigItem::IPv4 { name } => ResultingGridCell::Cell(DocGridCellType::Item { text: vec![ UserConfigItem::IPv4 { name } => ResultingGridCell::Cell(DocGridCellType::Item {
name.to_string(), "[Address IPv4]".to_string() text: vec![name.to_string(), "[Address IPv4]".to_string()],
]}), }),
UserConfigItem::Boolean { name } => ResultingGridCell::Cell(DocGridCellType::Item { text: vec![ UserConfigItem::Boolean { name } => ResultingGridCell::Cell(DocGridCellType::Item {
name.to_string(), "[Yes/No]".to_string() text: vec![name.to_string(), "[Yes/No]".to_string()],
] }), }),
UserConfigItem::Number { name, max, min } => ResultingGridCell::Cell(DocGridCellType::Item { text: vec![ UserConfigItem::Number { name, max, min } => {
name.to_string(), ResultingGridCell::Cell(DocGridCellType::Item {
match (min, max) { text: vec![
(None, None) => "[Integers]".to_string(), name.to_string(),
(None, Some(h)) => format!("[Integers: ..{}]", h), match (min, max) {
(Some(l), None) => format!("[Integers: {}..]", l), (None, None) => "[Integers]".to_string(),
(Some(l), Some(h)) => format!("[Integers: {}..{}]", l, h), (None, Some(h)) => format!("[Integers: ..{}]", h),
} (Some(l), None) => format!("[Integers: {}..]", l),
] }), (Some(l), Some(h)) => format!("[Integers: {}..{}]", l, h),
UserConfigItem::Submenu(user_config_data) => ResultingGridCell::Grid(DocGrid::from(user_config_data)), },
],
})
}
UserConfigItem::Submenu(user_config_data) => {
ResultingGridCell::Grid(DocGrid::from(user_config_data))
}
} }
} }
@@ -64,12 +71,16 @@ fn user_config_item_to_grid_cells(item: &UserConfigItem) -> ResultingGridCell {
pub struct DocGrid { pub struct DocGrid {
rows: usize, rows: usize,
cols: usize, cols: usize,
array_cells: Vec<DocGridCellType> array_cells: Vec<DocGridCellType>,
} }
impl DocGrid { impl DocGrid {
pub fn new() -> Self { pub fn new() -> Self {
Self { rows: 0, cols: 2, array_cells: Vec::new() } Self {
rows: 0,
cols: 2,
array_cells: Vec::new(),
}
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
@@ -81,23 +92,25 @@ impl DocGrid {
} }
pub fn set_title(&mut self, cell: DocGridCellType) { pub fn set_title(&mut self, cell: DocGridCellType) {
self.array_cells.resize(self.len() + self.cols, DocGridCellType::Empty); self.array_cells
.resize(self.len() + self.cols, DocGridCellType::Empty);
self.array_cells[0] = cell; self.array_cells[0] = cell;
} }
pub fn append_item(&mut self, item: ResultingGridCell) { pub fn append_item(&mut self, item: ResultingGridCell) {
match item { match item {
ResultingGridCell::Cell(cell_type) => { ResultingGridCell::Cell(cell_type) => {
self.array_cells.resize(self.len() + self.cols, DocGridCellType::Empty); self.array_cells
self.array_cells[self.rows*self.cols + 1] = cell_type; .resize(self.len() + self.cols, DocGridCellType::Empty);
self.array_cells[self.rows * self.cols + 1] = cell_type;
self.rows += 1; self.rows += 1;
}, }
ResultingGridCell::Grid(grid) => { ResultingGridCell::Grid(grid) => {
let new_col_size = std::cmp::max(self.cols, grid.cols + 1); let new_col_size = std::cmp::max(self.cols, grid.cols + 1);
let new_row_size = self.rows + grid.rows; let new_row_size = self.rows + grid.rows;
self.array_cells.resize(new_col_size * new_row_size, DocGridCellType::Empty); self.array_cells
.resize(new_col_size * new_row_size, DocGridCellType::Empty);
let row_offset = self.rows; let row_offset = self.rows;
let old_cols = self.cols; let old_cols = self.cols;
@@ -121,10 +134,8 @@ impl DocGrid {
// Copy in new stuff // Copy in new stuff
for row_index in 0..grid.rows { for row_index in 0..grid.rows {
for col_index in 0..grid.cols { for col_index in 0..grid.cols {
let (target_row_index, target_col_index) = ( let (target_row_index, target_col_index) =
row_index + row_offset, (row_index + row_offset, col_index + 1);
col_index + 1
);
let target_index = target_col_index + target_row_index * self.cols; let target_index = target_col_index + target_row_index * self.cols;
let src_index = row_index * grid.cols + col_index; let src_index = row_index * grid.cols + col_index;
@@ -142,7 +153,10 @@ impl DocGrid {
for row_index in 0..self.rows { for row_index in 0..self.rows {
s.push_str("| "); s.push_str("| ");
for col_index in 0..self.cols { for col_index in 0..self.cols {
s.push_str(&format!("{} ", self.array_cells[row_index * self.cols + col_index].to_debug_string())); s.push_str(&format!(
"{} ",
self.array_cells[row_index * self.cols + col_index].to_debug_string()
));
} }
s.push_str("|\n") s.push_str("|\n")
} }
@@ -152,21 +166,19 @@ impl DocGrid {
fn row_size_max(&self, c: usize) -> usize { fn row_size_max(&self, c: usize) -> usize {
let max = (0..self.rows) let max = (0..self.rows)
.map(|r| { match &self.array_cells[r*self.cols + c] { .map(|r| match &self.array_cells[r * self.cols + c] {
DocGridCellType::Boarder => 1, DocGridCellType::Boarder => 1,
DocGridCellType::Empty => 1, DocGridCellType::Empty => 1,
DocGridCellType::SubMenu { text: name } => { DocGridCellType::SubMenu { text: name } => {
name name.iter().map(|s| s.len()).max().unwrap_or(0)
.iter() }
.map(|s| s.len()).max().unwrap_or(0)
},
DocGridCellType::Item { text: name } => { DocGridCellType::Item { text: name } => {
name name.iter().map(|s| s.len()).max().unwrap_or(0)
.iter() }
.map(|s| s.len()).max().unwrap_or(0)
},
DocGridCellType::ItemJoin => 1, DocGridCellType::ItemJoin => 1,
} }).max().unwrap_or(1); })
.max()
.unwrap_or(1);
max max
} }
@@ -181,17 +193,15 @@ impl DocGrid {
fn col_size_max(&self, r: usize) -> usize { fn col_size_max(&self, r: usize) -> usize {
let max = (0..self.cols) let max = (0..self.cols)
.map(|c| { match &self.array_cells[r*self.cols + c] { .map(|c| match &self.array_cells[r * self.cols + c] {
DocGridCellType::Boarder => 1, DocGridCellType::Boarder => 1,
DocGridCellType::Empty => 1, DocGridCellType::Empty => 1,
DocGridCellType::SubMenu { text: name } => { DocGridCellType::SubMenu { text: name } => name.len(),
name.len() DocGridCellType::Item { text: name } => name.len(),
},
DocGridCellType::Item { text: name } => {
name.len()
},
DocGridCellType::ItemJoin => 1, DocGridCellType::ItemJoin => 1,
} }).max().unwrap_or(1); })
.max()
.unwrap_or(1);
max max
} }
@@ -208,7 +218,9 @@ impl DocGrid {
impl From<&UserConfigData> for DocGrid { impl From<&UserConfigData> for DocGrid {
fn from(value: &UserConfigData) -> Self { fn from(value: &UserConfigData) -> Self {
let mut res = DocGrid::new(); let mut res = DocGrid::new();
res.set_title(DocGridCellType::SubMenu { text: split_string(&value.name) }); res.set_title(DocGridCellType::SubMenu {
text: split_string(&value.name),
});
value.config_items.iter().for_each(|i| { value.config_items.iter().for_each(|i| {
res.append_item(user_config_item_to_grid_cells(i)); res.append_item(user_config_item_to_grid_cells(i));
}); });
@@ -217,31 +229,34 @@ impl From<&UserConfigData> for DocGrid {
for row_index in 0..res.rows { for row_index in 0..res.rows {
for col_index in 2..res.cols { for col_index in 2..res.cols {
let index = col_index + row_index * res.cols; let index = col_index + row_index * res.cols;
match res.array_cells[col_index-1 + row_index * res.cols] { match res.array_cells[col_index - 1 + row_index * res.cols] {
DocGridCellType::Item { .. } => { DocGridCellType::Item { .. } => {
res.array_cells[index] = DocGridCellType::ItemJoin; res.array_cells[index] = DocGridCellType::ItemJoin;
}, }
DocGridCellType::ItemJoin => { DocGridCellType::ItemJoin => {
res.array_cells[index] = DocGridCellType::ItemJoin; res.array_cells[index] = DocGridCellType::ItemJoin;
} }
_ => {} _ => {}
} }
} }
} }
res res
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub enum DocGridLineType { pub enum DocGridLineType {
NONE, VERTICAL, HORIZONTAL NONE,
VERTICAL,
HORIZONTAL,
} }
#[derive(Clone)] #[derive(Clone)]
pub enum DocGridLineCrossType { pub enum DocGridLineCrossType {
NONE, FULL, VERTICAL, HORIZONTAL NONE,
FULL,
VERTICAL,
HORIZONTAL,
} }
pub struct DocGridLayout { pub struct DocGridLayout {
@@ -251,18 +266,17 @@ pub struct DocGridLayout {
lines_cross: Vec<DocGridLineCrossType>, lines_cross: Vec<DocGridLineCrossType>,
} }
impl DocGridLayout { impl DocGridLayout {
fn map_line_to_char(l: &DocGridLineType) -> String { fn map_line_to_char(l: &DocGridLineType) -> String {
match l { match l {
DocGridLineType::HORIZONTAL => "-".to_string(), DocGridLineType::HORIZONTAL => "-".to_string(),
DocGridLineType::NONE => " ".to_string(), DocGridLineType::NONE => " ".to_string(),
DocGridLineType::VERTICAL => "|".to_string() DocGridLineType::VERTICAL => "|".to_string(),
} }
} }
fn map_cross_to_chr(c: &DocGridLineCrossType) -> String { fn map_cross_to_chr(c: &DocGridLineCrossType) -> String {
match c { match c {
DocGridLineCrossType::NONE => " ".to_string(), DocGridLineCrossType::NONE => " ".to_string(),
DocGridLineCrossType::FULL => "+".to_string(), DocGridLineCrossType::FULL => "+".to_string(),
DocGridLineCrossType::VERTICAL => "|".to_string(), DocGridLineCrossType::VERTICAL => "|".to_string(),
@@ -270,7 +284,7 @@ impl DocGridLayout {
} }
} }
fn get_vert_line (&self, r: i32, c: i32) -> &DocGridLineType { fn get_vert_line(&self, r: i32, c: i32) -> &DocGridLineType {
if r < 0 || r >= (self.grid.rows as i32) || c < 0 || c > (self.grid.cols as i32) { if r < 0 || r >= (self.grid.rows as i32) || c < 0 || c > (self.grid.cols as i32) {
&DocGridLineType::NONE &DocGridLineType::NONE
} else { } else {
@@ -282,7 +296,7 @@ impl DocGridLayout {
self.lines_vert[r as usize * (self.grid.cols + 1) + c as usize] = v; self.lines_vert[r as usize * (self.grid.cols + 1) + c as usize] = v;
} }
fn get_hort_line (&self, r: i32, c: i32) -> &DocGridLineType { fn get_hort_line(&self, r: i32, c: i32) -> &DocGridLineType {
if r < 0 || r > (self.grid.rows as i32) || c < 0 || c >= (self.grid.cols as i32) { if r < 0 || r > (self.grid.rows as i32) || c < 0 || c >= (self.grid.cols as i32) {
&DocGridLineType::NONE &DocGridLineType::NONE
} else { } else {
@@ -294,8 +308,7 @@ impl DocGridLayout {
self.lines_hort[r as usize * (self.grid.cols) + c as usize] = v; self.lines_hort[r as usize * (self.grid.cols) + c as usize] = v;
} }
fn get_cross(&self, r: i32, c: i32) -> &DocGridLineCrossType {
fn get_cross (&self, r: i32, c: i32) -> &DocGridLineCrossType {
if r < 0 || r > (self.grid.rows as i32) || c < 0 || c > (self.grid.cols as i32) { if r < 0 || r > (self.grid.rows as i32) || c < 0 || c > (self.grid.cols as i32) {
&DocGridLineCrossType::NONE &DocGridLineCrossType::NONE
} else { } else {
@@ -303,28 +316,42 @@ impl DocGridLayout {
} }
} }
fn set_cross (&mut self, r: usize, c: usize, v: DocGridLineCrossType) { fn set_cross(&mut self, r: usize, c: usize, v: DocGridLineCrossType) {
self.lines_cross[r * (self.grid.cols + 1) + c] = v; self.lines_cross[r * (self.grid.cols + 1) + c] = v;
} }
pub fn new<FLineV, FLineH, FCross>(grid: &DocGrid, vertical_mapper: FLineV, horizontal_mapper: FLineH, cross_mapper: FCross) -> DocGridLayout pub fn new<FLineV, FLineH, FCross>(
where FLineV: Fn(&DocGridCellType, &DocGridCellType) -> DocGridLineType, grid: &DocGrid,
vertical_mapper: FLineV,
horizontal_mapper: FLineH,
cross_mapper: FCross,
) -> DocGridLayout
where
FLineV: Fn(&DocGridCellType, &DocGridCellType) -> DocGridLineType,
FLineH: Fn(&DocGridCellType, &DocGridCellType) -> DocGridLineType, FLineH: Fn(&DocGridCellType, &DocGridCellType) -> DocGridLineType,
FCross: Fn(&DocGridLineType, &DocGridLineType, &DocGridLineType, &DocGridLineType) -> DocGridLineCrossType FCross: Fn(
&DocGridLineType,
&DocGridLineType,
&DocGridLineType,
&DocGridLineType,
) -> DocGridLineCrossType,
{ {
let mut lines_hort = Vec::new(); let mut lines_hort = Vec::new();
let mut lines_vert = Vec::new(); let mut lines_vert = Vec::new();
let mut crosses = Vec::new(); let mut crosses = Vec::new();
lines_hort.resize(grid.cols * (1 + grid.rows), DocGridLineType::NONE); lines_hort.resize(grid.cols * (1 + grid.rows), DocGridLineType::NONE);
lines_vert.resize((grid.cols + 1) * grid.rows , DocGridLineType::NONE ); lines_vert.resize((grid.cols + 1) * grid.rows, DocGridLineType::NONE);
crosses.resize((grid.rows + 1) * (grid.cols + 1), DocGridLineCrossType::NONE); crosses.resize(
(grid.rows + 1) * (grid.cols + 1),
DocGridLineCrossType::NONE,
);
let mut res: DocGridLayout = DocGridLayout { let mut res: DocGridLayout = DocGridLayout {
grid: grid.clone(), grid: grid.clone(),
lines_cross: crosses, lines_cross: crosses,
lines_hort: lines_hort, lines_hort: lines_hort,
lines_vert: lines_vert lines_vert: lines_vert,
}; };
let get_cell = |r: i32, c: i32| -> &DocGridCellType { let get_cell = |r: i32, c: i32| -> &DocGridCellType {
@@ -334,28 +361,26 @@ impl DocGridLayout {
&grid.array_cells[r as usize * grid.cols + c as usize] &grid.array_cells[r as usize * grid.cols + c as usize]
} }
}; };
// Calculating Lines Horizontal lines // Calculating Lines Horizontal lines
for row in 0..(grid.rows + 1) { for row in 0..(grid.rows + 1) {
for col in 0..grid.cols { for col in 0..grid.cols {
let top = get_cell(row as i32 - 1 , col as i32); let top = get_cell(row as i32 - 1, col as i32);
let bottom = get_cell(row as i32 , col as i32); let bottom = get_cell(row as i32, col as i32);
res.set_hort_line(row, col, horizontal_mapper(top, bottom)); res.set_hort_line(row, col, horizontal_mapper(top, bottom));
} }
} }
// Calculating Lines Vertical lines // Calculating Lines Vertical lines
for row in 0..grid.get_row() { for row in 0..grid.get_row() {
for col in 0..(grid.cols + 1) { for col in 0..(grid.cols + 1) {
let left = get_cell(row as i32, col as i32 - 1); let left = get_cell(row as i32, col as i32 - 1);
let right = get_cell(row as i32 , col as i32); let right = get_cell(row as i32, col as i32);
res.set_vert_line(row, col, vertical_mapper(left, right)); res.set_vert_line(row, col, vertical_mapper(left, right));
} }
} }
// Calculating Crossings // Calculating Crossings
for row in 0..(grid.rows + 1) { for row in 0..(grid.rows + 1) {
for col in 0..(grid.cols + 1) { for col in 0..(grid.cols + 1) {
let north = res.get_vert_line((row as i32) - 1, col as i32); let north = res.get_vert_line((row as i32) - 1, col as i32);
@@ -376,30 +401,41 @@ impl DocGridLayout {
for r in 0..self.grid.rows { for r in 0..self.grid.rows {
for c in 0..self.grid.cols { for c in 0..self.grid.cols {
s.push_str(&format!("{}{}", s.push_str(&format!(
&Self::map_cross_to_chr(self.get_cross(r as i32, c as i32)), "{}{}",
&Self::map_cross_to_chr(self.get_cross(r as i32, c as i32)),
&Self::map_line_to_char(self.get_hort_line(r as i32, c as i32)) &Self::map_line_to_char(self.get_hort_line(r as i32, c as i32))
)); ));
} }
s.push_str(&format!("{}\n", &Self::map_cross_to_chr(self.get_cross(r as i32, self.grid.cols as i32)))); s.push_str(&format!(
"{}\n",
&Self::map_cross_to_chr(self.get_cross(r as i32, self.grid.cols as i32))
));
for c in 0..self.grid.cols { for c in 0..self.grid.cols {
s.push_str(&format!("{}{}", s.push_str(&format!(
"{}{}",
&Self::map_line_to_char(self.get_vert_line(r as i32, c as i32)), &Self::map_line_to_char(self.get_vert_line(r as i32, c as i32)),
&self.grid.array_cells[self.grid.cols * r + c].to_debug_string() &self.grid.array_cells[self.grid.cols * r + c].to_debug_string()
)); ));
} }
s.push_str(&format!("{}\n", s.push_str(&format!(
&Self::map_line_to_char(self.get_vert_line(r as i32, self.grid.cols as i32)))); "{}\n",
}; &Self::map_line_to_char(self.get_vert_line(r as i32, self.grid.cols as i32))
));
}
for c in 0..self.grid.cols { for c in 0..self.grid.cols {
s.push_str(&format!("{}{}", s.push_str(&format!(
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, c as i32)), "{}{}",
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, c as i32)),
&Self::map_line_to_char(self.get_hort_line(self.grid.rows as i32, c as i32)) &Self::map_line_to_char(self.get_hort_line(self.grid.rows as i32, c as i32))
)); ));
} }
s.push_str(&format!("{}\n", &Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, self.grid.cols as i32)))); s.push_str(&format!(
"{}\n",
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, self.grid.cols as i32))
));
s s
} }
@@ -415,71 +451,88 @@ impl DocGridLayout {
match cell { match cell {
DocGridCellType::Boarder => empty_string.clone(), DocGridCellType::Boarder => empty_string.clone(),
DocGridCellType::Empty => empty_string.clone(), DocGridCellType::Empty => empty_string.clone(),
DocGridCellType::SubMenu { text } => text.get(*sub_c).cloned().unwrap_or(empty_string.clone()), DocGridCellType::SubMenu { text } => {
DocGridCellType::Item { text } => text.get(*sub_c).cloned().unwrap_or(empty_string.clone()), text.get(*sub_c).cloned().unwrap_or(empty_string.clone())
}
DocGridCellType::Item { text } => {
text.get(*sub_c).cloned().unwrap_or(empty_string.clone())
}
DocGridCellType::ItemJoin => empty_string.clone(), DocGridCellType::ItemJoin => empty_string.clone(),
} }
}; };
for r in 0..self.grid.rows { for r in 0..self.grid.rows {
for c in 0..self.grid.cols { for c in 0..self.grid.cols {
s.push_str(&format!("{}{}", s.push_str(&format!(
&Self::map_cross_to_chr(self.get_cross(r as i32, c as i32)), "{}{}",
&Self::map_line_to_char(self.get_hort_line(r as i32, c as i32)).repeat(row_sizes[c] + 2) &Self::map_cross_to_chr(self.get_cross(r as i32, c as i32)),
&Self::map_line_to_char(self.get_hort_line(r as i32, c as i32))
.repeat(row_sizes[c] + 2)
)); ));
} }
s.push_str(&format!("{}\n", &Self::map_cross_to_chr(self.get_cross(r as i32, self.grid.cols as i32)))); s.push_str(&format!(
"{}\n",
&Self::map_cross_to_chr(self.get_cross(r as i32, self.grid.cols as i32))
));
for sub_c in 0..col_sizes[r] { for sub_c in 0..col_sizes[r] {
for c in 0..self.grid.cols { for c in 0..self.grid.cols {
s.push_str(&format!("{} {} ", s.push_str(&format!(
"{} {} ",
&Self::map_line_to_char(self.get_vert_line(r as i32, c as i32)), &Self::map_line_to_char(self.get_vert_line(r as i32, c as i32)),
&map_cell(&self.grid.array_cells[self.grid.cols * r + c], &sub_c).pad_to_width(row_sizes[c]) &map_cell(&self.grid.array_cells[self.grid.cols * r + c], &sub_c)
.pad_to_width(row_sizes[c])
)); ));
} }
s.push_str(&format!("{}\n", s.push_str(&format!(
&Self::map_line_to_char(self.get_vert_line(r as i32, self.grid.cols as i32)))); "{}\n",
&Self::map_line_to_char(self.get_vert_line(r as i32, self.grid.cols as i32))
));
} }
}; }
for c in 0..self.grid.cols { for c in 0..self.grid.cols {
s.push_str(&format!("{}{}", s.push_str(&format!(
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, c as i32)), "{}{}",
&Self::map_line_to_char(self.get_hort_line(self.grid.rows as i32, c as i32)).repeat(row_sizes[c] + 2) &Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, c as i32)),
&Self::map_line_to_char(self.get_hort_line(self.grid.rows as i32, c as i32))
.repeat(row_sizes[c] + 2)
)); ));
} }
s.push_str(&format!("{}\n", &Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, self.grid.cols as i32)))); s.push_str(&format!(
"{}\n",
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, self.grid.cols as i32))
));
s s
} }
} }
mod doc_grid_layout_mappers { mod doc_grid_layout_mappers {
use super::{DocGridLineType, DocGridLineCrossType, DocGridCellType}; use super::{DocGridCellType, DocGridLineCrossType, DocGridLineType};
pub fn vert_line_ascii(left: &DocGridCellType, right: &DocGridCellType) -> DocGridLineType { pub fn vert_line_ascii(left: &DocGridCellType, right: &DocGridCellType) -> DocGridLineType {
use DocGridCellType::{ItemJoin, Item}; use DocGridCellType::{Item, ItemJoin};
match (left, right) { match (left, right) {
(ItemJoin, Item { .. }) => DocGridLineType::NONE, (ItemJoin, Item { .. }) => DocGridLineType::NONE,
(Item { .. }, ItemJoin) => DocGridLineType::NONE, (Item { .. }, ItemJoin) => DocGridLineType::NONE,
(_, _) => DocGridLineType::VERTICAL (_, _) => DocGridLineType::VERTICAL,
} }
} }
pub fn hort_line_ascii(top: &DocGridCellType, bottom: &DocGridCellType) -> DocGridLineType { pub fn hort_line_ascii(top: &DocGridCellType, bottom: &DocGridCellType) -> DocGridLineType {
use DocGridCellType::{Boarder, Empty, ItemJoin, Item, SubMenu}; use DocGridCellType::{Boarder, Empty, Item, ItemJoin, SubMenu};
match (top, bottom) { match (top, bottom) {
(Empty, Empty) => DocGridLineType::NONE, (Empty, Empty) => DocGridLineType::NONE,
// Sub Menu "Joins" // Sub Menu "Joins"
(Empty, SubMenu { .. }) => DocGridLineType::HORIZONTAL, (Empty, SubMenu { .. }) => DocGridLineType::HORIZONTAL,
(SubMenu { .. }, Empty) => DocGridLineType::NONE, (SubMenu { .. }, Empty) => DocGridLineType::NONE,
(SubMenu { .. }, SubMenu { .. }) => DocGridLineType::HORIZONTAL, (SubMenu { .. }, SubMenu { .. }) => DocGridLineType::HORIZONTAL,
// Horizontal Rules // Horizontal Rules
(Boarder, _) => DocGridLineType::HORIZONTAL, (Boarder, _) => DocGridLineType::HORIZONTAL,
(_, Boarder) => DocGridLineType::HORIZONTAL, (_, Boarder) => DocGridLineType::HORIZONTAL,
@@ -491,8 +544,13 @@ mod doc_grid_layout_mappers {
} }
} }
pub fn cross_ascii(n: &DocGridLineType, e: &DocGridLineType, s: &DocGridLineType, w: &DocGridLineType) -> DocGridLineCrossType{ pub fn cross_ascii(
use DocGridLineType::{NONE, HORIZONTAL, VERTICAL}; n: &DocGridLineType,
e: &DocGridLineType,
s: &DocGridLineType,
w: &DocGridLineType,
) -> DocGridLineCrossType {
use DocGridLineType::{HORIZONTAL, NONE, VERTICAL};
match (n, e, s, w) { match (n, e, s, w) {
(VERTICAL, NONE, VERTICAL, NONE) => DocGridLineCrossType::VERTICAL, (VERTICAL, NONE, VERTICAL, NONE) => DocGridLineCrossType::VERTICAL,
(NONE, HORIZONTAL, NONE, HORIZONTAL) => DocGridLineCrossType::HORIZONTAL, (NONE, HORIZONTAL, NONE, HORIZONTAL) => DocGridLineCrossType::HORIZONTAL,
@@ -502,20 +560,22 @@ mod doc_grid_layout_mappers {
} }
pub fn create_ascii_debug_string(grid: &DocGrid) -> String { pub fn create_ascii_debug_string(grid: &DocGrid) -> String {
let layout = DocGridLayout::new(grid, let layout = DocGridLayout::new(
grid,
doc_grid_layout_mappers::vert_line_ascii, doc_grid_layout_mappers::vert_line_ascii,
doc_grid_layout_mappers::hort_line_ascii, doc_grid_layout_mappers::hort_line_ascii,
doc_grid_layout_mappers::cross_ascii doc_grid_layout_mappers::cross_ascii,
); );
return layout.to_debug_string(); return layout.to_debug_string();
} }
pub fn create_ascii_doc_string(grid: &DocGrid) -> String { pub fn create_ascii_doc_string(grid: &DocGrid) -> String {
let layout = DocGridLayout::new(grid, let layout = DocGridLayout::new(
grid,
doc_grid_layout_mappers::vert_line_ascii, doc_grid_layout_mappers::vert_line_ascii,
doc_grid_layout_mappers::hort_line_ascii, doc_grid_layout_mappers::hort_line_ascii,
doc_grid_layout_mappers::cross_ascii doc_grid_layout_mappers::cross_ascii,
); );
return layout.to_ascii_doc_string(); return layout.to_ascii_doc_string();
@@ -523,44 +583,63 @@ pub fn create_ascii_doc_string(grid: &DocGrid) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::configotron::{conf::{UserConfigData, UserConfigItem}};
use super::{DocGrid, DocGridCellType, ResultingGridCell}; use super::{DocGrid, DocGridCellType, ResultingGridCell};
use crate::configotron::conf::{UserConfigData, UserConfigItem};
// Helper functino for creating elements for testing // Helper functino for creating elements for testing
fn create_item() -> UserConfigItem { fn create_item() -> UserConfigItem {
UserConfigItem::Boolean { name: "Test".to_string() } UserConfigItem::Boolean {
name: "Test".to_string(),
}
} }
fn create_submenu(v: Vec<UserConfigItem>) -> UserConfigItem { fn create_submenu(v: Vec<UserConfigItem>) -> UserConfigItem {
UserConfigItem::Submenu(UserConfigData { name: "SubMenu".to_string(), config_items: v }) UserConfigItem::Submenu(UserConfigData {
name: "SubMenu".to_string(),
config_items: v,
})
} }
fn create_menu(v: Vec<UserConfigItem>) -> UserConfigData { fn create_menu(v: Vec<UserConfigItem>) -> UserConfigData {
UserConfigData { name: "TestMenu".to_string(), config_items: v } UserConfigData {
name: "TestMenu".to_string(),
config_items: v,
}
} }
// Actuall Test // Actuall Test
#[test] #[test]
fn grid_extend() { fn grid_extend() {
let mut grid = DocGrid::new(); let mut grid = DocGrid::new();
grid.set_title(DocGridCellType::SubMenu { text: vec!["name".to_string()] });
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item { text: vec!["name".to_string()] }));
grid.append_item(ResultingGridCell::Cell(DocGridCellType::ItemJoin));
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item { text: vec!["name".to_string()] }));
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item { text: vec!["name".to_string()] }));
grid.append_item(ResultingGridCell::Cell(DocGridCellType::ItemJoin));
grid.append_item(ResultingGridCell::Cell(DocGridCellType::SubMenu { text: vec!["name".to_string()] }));
assert_eq!(grid.to_debug_string(), r#"| S I | grid.set_title(DocGridCellType::SubMenu {
text: vec!["name".to_string()],
});
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item {
text: vec!["name".to_string()],
}));
grid.append_item(ResultingGridCell::Cell(DocGridCellType::ItemJoin));
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item {
text: vec!["name".to_string()],
}));
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item {
text: vec!["name".to_string()],
}));
grid.append_item(ResultingGridCell::Cell(DocGridCellType::ItemJoin));
grid.append_item(ResultingGridCell::Cell(DocGridCellType::SubMenu {
text: vec!["name".to_string()],
}));
assert_eq!(
grid.to_debug_string(),
r#"| S I |
| E i | | E i |
| E I | | E I |
| E I | | E I |
| E i | | E i |
| E S | | E S |
"#); "#
);
} }
#[test] #[test]
@@ -568,19 +647,17 @@ mod tests {
let data = create_menu(vec![ let data = create_menu(vec![
create_item(), create_item(),
create_item(), create_item(),
create_submenu(vec![ create_submenu(vec![create_item(), create_item(), create_item()]),
create_item(), create_item(),
create_item(),
create_item(),
]),
create_item(), create_item(),
create_item(), create_item(),
create_item()
]); ]);
let grid = DocGrid::from(&data); let grid = DocGrid::from(&data);
assert_eq!(grid.to_debug_string(), r#"| S I i | assert_eq!(
grid.to_debug_string(),
r#"| S I i |
| E I i | | E I i |
| E S I | | E S I |
| E E I | | E E I |
@@ -588,61 +665,55 @@ mod tests {
| E I i | | E I i |
| E I i | | E I i |
| E I i | | E I i |
"#); "#
);
} }
#[test] #[test]
fn grid_deep_assembly_from_data() { fn grid_deep_assembly_from_data() {
let data = create_menu(vec![ let data = create_menu(vec![create_submenu(vec![create_submenu(vec![
create_submenu(vec![ create_submenu(vec![create_item()]),
create_submenu(vec![ ])])]);
create_submenu(vec![
create_item()
]),
]),
]),
]);
let grid = DocGrid::from(&data); let grid = DocGrid::from(&data);
assert_eq!(grid.to_debug_string(), r#"| S S S S I | assert_eq!(
"#); grid.to_debug_string(),
r#"| S S S S I |
"#
);
} }
#[test] #[test]
fn grid_complex_assembly_from_data() { fn grid_complex_assembly_from_data() {
let data = create_menu(vec![ let data = create_menu(vec![
create_item(),
create_item(),
create_submenu(vec![
create_item(), create_item(),
create_item(), create_item(),
create_submenu(vec![ create_submenu(vec![
create_item(), create_item(),
create_item(), create_item(),
create_submenu(vec![ create_submenu(vec![
create_item(), create_item(),
create_item(), create_item(),
create_submenu(vec![create_item(), create_item()]),
create_item(),
]), ]),
create_item(), create_item(),
]), create_submenu(vec![create_item(), create_item()]),
create_item(),
create_submenu(vec![
create_item(), create_item(),
create_item(), create_item(),
]), ]),
create_item(), create_item(),
create_item(), create_item(),
]),
create_item(),
create_item(),
]); ]);
let grid = DocGrid::from(&data); let grid = DocGrid::from(&data);
println!("{}", grid.to_debug_string()); println!("{}", grid.to_debug_string());
assert_eq!(grid.to_debug_string(), r#"| S I i i i | assert_eq!(
grid.to_debug_string(),
r#"| S I i i i |
| E I i i i | | E I i i i |
| E S I i i | | E S I i i |
| E E I i i | | E E I i i |
@@ -658,37 +729,38 @@ mod tests {
| E E I i i | | E E I i i |
| E I i i i | | E I i i i |
| E I i i i | | E I i i i |
"#); "#
);
} }
mod ascii_docs { mod ascii_docs {
use crate::configotron::{conf::{UserConfigData, UserConfigItem}, doc::grid::create_ascii_debug_string, doc::grid::create_ascii_doc_string}; use super::{create_item, create_menu, create_submenu, DocGrid};
use super::{DocGrid, create_item, create_menu, create_submenu}; use crate::configotron::{
doc::grid::create_ascii_debug_string, doc::grid::create_ascii_doc_string,
};
#[test] #[test]
fn ascii_docs_debug() { fn ascii_docs_debug() {
let data = create_menu(vec![ let data = create_menu(vec![
create_item(), create_item(),
create_item(), create_item(),
create_submenu(vec![ create_submenu(vec![create_item(), create_item(), create_item()]),
create_item(), create_item(),
create_item(),
create_item(),
]),
create_item(), create_item(),
create_item(), create_item(),
create_item()
]); ]);
let grid = DocGrid::from(&data); let grid = DocGrid::from(&data);
println!("{}", grid.to_debug_string()); println!("{}", grid.to_debug_string());
let s = create_ascii_debug_string(&grid); let s = create_ascii_debug_string(&grid);
println!("{}", s); println!("{}", s);
assert_eq!(s, r#"+-+---+ assert_eq!(
s,
r#"+-+---+
|S|I i| |S|I i|
| +---+ | +---+
|E|I i| |E|I i|
@@ -705,51 +777,58 @@ mod tests {
| +---+ | +---+
|E|I i| |E|I i|
+-+---+ +-+---+
"#); "#
} );
}
#[test] #[test]
fn ascii_docs_real() { fn ascii_docs_real() {
let data = create_menu(vec![ let data = create_menu(vec![
create_item(), create_item(),
create_item(), create_item(),
create_submenu(vec![ create_submenu(vec![create_item(), create_item(), create_item()]),
create_item(), create_item(),
create_item(),
create_item(),
]),
create_item(), create_item(),
create_item(), create_item(),
create_item()
]); ]);
let grid = DocGrid::from(&data); let grid = DocGrid::from(&data);
println!("{}", grid.to_debug_string()); println!("{}", grid.to_debug_string());
let s = create_ascii_doc_string(&grid); let s = create_ascii_doc_string(&grid);
println!("{}", s); println!("{}", s);
assert_eq!(s, r#"+----------+----------------+ assert_eq!(
| TestMenu | Test | s,
| +----------------+ r#"+----------+---------------------+
| | Test | | TestMenu | Test |
| +---------+------+ | | [Yes/No] |
| | SubMenu | Test | | +---------------------+
| | +------+ | | Test |
| | | Test | | | [Yes/No] |
| | +------+ | +----------+----------+
| | | Test | | | SubMenu | Test |
| +---------+------+ | | | [Yes/No] |
| | Test | | | +----------+
| +----------------+ | | | Test |
| | Test | | | | [Yes/No] |
| +----------------+ | | +----------+
| | Test | | | | Test |
+----------+----------------+ | | | [Yes/No] |
"#); | +----------+----------+
| | Test |
| | [Yes/No] |
| +---------------------+
| | Test |
| | [Yes/No] |
| +---------------------+
| | Test |
| | [Yes/No] |
+----------+---------------------+
"#
);
}
} }
}
}
}

View File

@@ -1,137 +1,288 @@
use std::{cell::RefCell, rc::{Weak, Rc}}; 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 { pub struct FSM {
states: Vec<Rc<RefCell<FSMState>>> name: String,
states: Vec<Rc<RefCell<FSMState>>>,
} }
impl FSM { impl FSM {
pub fn new(state: Vec<Rc<RefCell<FSMState>>>) -> Self { pub fn new(state: Vec<Rc<RefCell<FSMState>>>, name: &str) -> Self {
FSM {states: state} 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)] #[derive(Debug, Clone, Copy)]
pub enum FSMStateAction { pub enum FSMStateAction {
NONE, TERMINAL NONE,
TERMINAL,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FSMState { pub struct FSMState {
name: String, name: String,
action: FSMStateAction, action: FSMStateAction,
transition: Option<FSMTranistion> transition: Option<tranisitions::FSMTranistion>,
} }
#[derive(Debug, Clone)]
pub struct FSMTranistion {
array: [Weak<RefCell<FSMState>>; 128],
}
impl FSMState { impl FSMState {
pub fn new(name: String, action: FSMStateAction) -> Self { pub fn new(name: String, action: FSMStateAction) -> Self {
FSMState { name: name, action: action, transition: None } FSMState {
name: name,
action: action,
transition: None,
}
} }
pub fn set_transitions(&mut self, t: FSMTranistion) { pub fn set_transitions(&mut self, t: tranisitions::FSMTranistion) {
self.transition = Some(t); self.transition = Some(t);
} }
}
impl FSMTranistion { pub fn to_graph_label(&self) -> String {
return format!("\"{}\"", self.name);
pub fn new_from_mask(mask: TransitionMask, pos: Weak<RefCell<FSMState>>, default: Weak<RefCell<FSMState>>) -> Self {
let mut array: [Weak<RefCell<FSMState>>; 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(mask: TransitionMask, default: Weak<RefCell<FSMState>>) -> Self { pub fn to_graph_hash(&self) -> String {
let mut array: [Weak<RefCell<FSMState>>; 128] = std::array::from_fn(|_| Weak::clone(&default)); self.to_graph_label()
FSMTranistion { array }
}
pub fn apply(&mut self, mask: TransitionMask, state: Weak<RefCell<FSMState>>) {
for i in 0..128 {
if mask.get_bit(i) {
self.array[i] = Weak::clone(&state );
}
}
} }
} }
use transition_masking::{TransitionMask}; 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 { mod transition_masking {
use std::ops::RangeInclusive; use itertools::Itertools;
use std::{ops::RangeInclusive, usize};
pub enum MaskPresets { pub enum MaskPresets {
Char(char), Char(char),
AnyCase, LowerCase, UpperCase, AnyCase,
LowerCase,
UpperCase,
AllHumanReadable, AllHumanReadable,
Numbers, SpecialChars, Numbers,
WhiteSpace, LineTermination, SpecialChars,
NoChar WhiteSpace,
LineTermination,
NoChar,
} }
#[derive(Debug, Clone)]
pub struct TransitionMask { pub struct TransitionMask {
mask: u128 mask: u128,
} }
impl From<MaskPresets> for TransitionMask { impl From<MaskPresets> for TransitionMask {
fn from(value: MaskPresets) -> Self { fn from(value: MaskPresets) -> Self {
let mut n: u128 = 0; let mut n: u128 = 0;
let mut set_range = |r: RangeInclusive<i32>| { let mut set_range = |r: RangeInclusive<i32>| {
r.for_each(|i| { n |= 1 << i }); r.for_each(|i| n |= 1 << i);
}; };
match value { match value {
MaskPresets::NoChar => {} MaskPresets::NoChar => {}
MaskPresets::AnyCase => { MaskPresets::AnyCase => {
set_range(0x41..=0x5a); // UpperCase set_range(0x41..=0x5a); // UpperCase
set_range(0x61..=0x7a); // LowerCase set_range(0x61..=0x7a); // LowerCase
}, }
MaskPresets::LowerCase => { MaskPresets::LowerCase => {
set_range(0x61..=0x7a); // LowerCase set_range(0x61..=0x7a); // LowerCase
}, }
MaskPresets::UpperCase => { MaskPresets::UpperCase => {
set_range(0x41..=0x5a); // UpperCase set_range(0x41..=0x5a); // UpperCase
}, }
MaskPresets::AllHumanReadable => { MaskPresets::AllHumanReadable => {
set_range(0x20..=0x7e); // All Ascii Chars + Space + Tab set_range(0x20..=0x7e); // All Ascii Chars + Space + Tab
n |= 1 << 0x9; // Tab n |= 1 << 0x9; // Tab
}, }
MaskPresets::Numbers => { MaskPresets::Numbers => {
set_range(0x30..=0x39); // Numbers set_range(0x30..=0x39); // Numbers
}, }
MaskPresets::SpecialChars => { MaskPresets::SpecialChars => {
set_range(0x21..=0x2F); // ! to / set_range(0x21..=0x2F); // ! to /
set_range(0x3A..=0x40); // : to @ set_range(0x3A..=0x40); // : to @
set_range(0x5B..=0x0D); // [ to ` set_range(0x5B..=0x0D); // [ to `
set_range(0x7B..=0x7E); // { to ~ set_range(0x7B..=0x7E); // { to ~
}, }
MaskPresets::WhiteSpace => { MaskPresets::WhiteSpace => {
n |= 1 << 0x9; // Tab n |= 1 << 0x9; // Tab
n |= 1 << 0x20; // Spaces n |= 1 << 0x20; // Spaces
}
},
MaskPresets::LineTermination => { MaskPresets::LineTermination => {
n |= 1 << 0x0a; // Line Feed n |= 1 << 0x0a; // Line Feed
n |= 1 << 0x0d; // Carriage Return n |= 1 << 0x0d; // Carriage Return
} }
MaskPresets::Char(c) => { MaskPresets::Char(c) => {
if c.is_ascii() { if c.is_ascii() {
n |= 1 << (c as u32) n |= 1 << (c as u32)
} }
}, }
} }
TransitionMask { mask: n } TransitionMask { mask: n }
@@ -144,11 +295,15 @@ mod transition_masking {
} }
pub fn or(&self, other: Self) -> TransitionMask { pub fn or(&self, other: Self) -> TransitionMask {
TransitionMask { mask: other.mask | self.mask } TransitionMask {
mask: other.mask | self.mask,
}
} }
pub fn and(&self, other: Self) -> TransitionMask { pub fn and(&self, other: Self) -> TransitionMask {
TransitionMask { mask: other.mask & self.mask } TransitionMask {
mask: other.mask & self.mask,
}
} }
pub fn inv(&self) -> TransitionMask { pub fn inv(&self) -> TransitionMask {
@@ -158,42 +313,190 @@ mod transition_masking {
pub fn get_bit(&self, i: usize) -> bool { pub fn get_bit(&self, i: usize) -> bool {
(self.mask & (1 << i)) > 0 (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)] #[cfg(test)]
mod test { mod test {
use super::{transition_masking::*, *}; use graphviz_rust::cmd::Format;
use std::io::Write;
use super::{tranisitions::*, transition_masking::*, *};
#[test] #[test]
fn simple_fsm_building() { fn transition_mask_to_string() {
let z0 = Rc::new(RefCell::new(FSMState::new("z0".to_string(), FSMStateAction::NONE))); let mut mask1 = TransitionMask::new();
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))); mask1.set_bit(0);
let z3 = Rc::new(RefCell::new(FSMState::new("z3".to_string(), FSMStateAction::NONE)));
(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( let mut z0_transisions = FSMTranistion::new_from_mask(
TransitionMask::from(MaskPresets::Char('a')), Rc::downgrade(&z1), Rc::downgrade(&z3)); TransitionMask::from(MaskPresets::Char('a')),
z0_transisions.apply(TransitionMask::from(MaskPresets::Char('b')), Rc::downgrade(&z2)); 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( let mut z1_transisions = FSMTranistion::new_from_mask(
TransitionMask::from(MaskPresets::Char('a')), Rc::downgrade(&z1), Rc::downgrade(&z3)); TransitionMask::from(MaskPresets::Char('a')),
z1_transisions.apply(TransitionMask::from(MaskPresets::Char('b')), Rc::downgrade(&z2)); Rc::downgrade(&z1),
Rc::downgrade(&z3),
);
z1_transisions.apply(
TransitionMask::from(MaskPresets::Char('b')),
Rc::downgrade(&z2),
);
let z2_transisions = FSMTranistion::new_from_mask( let z2_transisions = FSMTranistion::new_from_mask(
TransitionMask::from(MaskPresets::Char('a')), Rc::downgrade(&z2), Rc::downgrade(&z3)); TransitionMask::from(MaskPresets::Char('a')),
Rc::downgrade(&z2),
Rc::downgrade(&z3),
);
let z3_transisions = FSMTranistion::new(Rc::downgrade(&z3));
let z3_transisions = FSMTranistion::new(
TransitionMask::from(MaskPresets::Char('a')), Rc::downgrade(&z3));
z0.borrow_mut().set_transitions(z0_transisions); z0.borrow_mut().set_transitions(z0_transisions);
z1.borrow_mut().set_transitions(z1_transisions); z1.borrow_mut().set_transitions(z1_transisions);
z2.borrow_mut().set_transitions(z2_transisions); z2.borrow_mut().set_transitions(z2_transisions);
z3.borrow_mut().set_transitions(z3_transisions); z3.borrow_mut().set_transitions(z3_transisions);
let fsm = FSM::new(vec![z0, z1, z2, z3], "Test FSM #1");
let fsm = FSM::new(vec![z0, z1, z2, z3]); 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());
} }
}
}

View File

@@ -1,3 +1,3 @@
pub mod conf; pub mod conf;
pub mod doc; pub mod doc;
pub mod fsm; pub mod fsm;

View File

@@ -1,10 +1,10 @@
#[allow(dead_code)]
mod configotron; mod configotron;
use configotron::conf::{read_and_parse_yaml_file}; use configotron::conf::read_and_parse_yaml_file;
use configotron::doc::grid::{DocGrid, create_ascii_doc_string}; use configotron::doc::grid::{create_ascii_doc_string, DocGrid};
fn main() { fn main() {
let data = match read_and_parse_yaml_file("example.yml") { let data = match read_and_parse_yaml_file("example.yml") {
Ok(d) => d, Ok(d) => d,
Err(error) => { Err(error) => {

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: root Pages: 1 -->
<svg width="490pt" height="162pt"
viewBox="0.00 0.00 489.69 162.07" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 158.07)">
<title>root</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-158.07 485.69,-158.07 485.69,4 -4,4"/>
<!-- z0 -->
<g id="node1" class="node">
<title>z0</title>
<ellipse fill="none" stroke="black" cx="21.45" cy="-39.63" rx="21.4" ry="21.4"/>
<text text-anchor="middle" x="21.45" y="-35.93" font-family="Times,serif" font-size="14.00">z0</text>
</g>
<!-- z2 -->
<g id="node2" class="node">
<title>z2</title>
<ellipse fill="none" stroke="black" cx="287.79" cy="-95.63" rx="21.4" ry="21.4"/>
<ellipse fill="none" stroke="black" cx="287.79" cy="-95.63" rx="25.4" ry="25.4"/>
<text text-anchor="middle" x="287.79" y="-91.93" font-family="Times,serif" font-size="14.00">z2</text>
</g>
<!-- z0&#45;&gt;z2 -->
<g id="edge1" class="edge">
<title>z0&#45;&gt;z2</title>
<path fill="none" stroke="black" d="M35.33,-56.2C48.41,-71.57 70.23,-93.4 94.9,-102.63 147.49,-122.3 213.96,-113.05 253.27,-104.43"/>
<polygon fill="black" stroke="black" points="254.17,-107.81 263.12,-102.15 252.59,-100.99 254.17,-107.81"/>
<text text-anchor="middle" x="116.34" y="-115.43" font-family="Times,serif" font-size="14.00">&#39;b&#39;</text>
</g>
<!-- z1 -->
<g id="node3" class="node">
<title>z1</title>
<ellipse fill="none" stroke="black" cx="116.34" cy="-39.63" rx="21.4" ry="21.4"/>
<text text-anchor="middle" x="116.34" y="-35.93" font-family="Times,serif" font-size="14.00">z1</text>
</g>
<!-- z0&#45;&gt;z1 -->
<g id="edge2" class="edge">
<title>z0&#45;&gt;z1</title>
<path fill="none" stroke="black" d="M43.06,-39.63C55.21,-39.63 70.82,-39.63 84.41,-39.63"/>
<polygon fill="black" stroke="black" points="84.79,-43.13 94.79,-39.63 84.79,-36.13 84.79,-43.13"/>
<text text-anchor="middle" x="68.9" y="-43.43" font-family="Times,serif" font-size="14.00">&#39;a&#39;</text>
</g>
<!-- z3 -->
<g id="node4" class="node">
<title>z3</title>
<ellipse fill="none" stroke="black" cx="460.24" cy="-37.63" rx="21.4" ry="21.4"/>
<text text-anchor="middle" x="460.24" y="-33.93" font-family="Times,serif" font-size="14.00">z3</text>
</g>
<!-- z0&#45;&gt;z3 -->
<g id="edge3" class="edge">
<title>z0&#45;&gt;z3</title>
<path fill="none" stroke="black" d="M40.58,-29.77C54.86,-22.59 75.54,-13.48 94.9,-9.63 217.24,14.7 366.58,-14.86 429.28,-29.88"/>
<polygon fill="black" stroke="black" points="428.72,-33.35 439.27,-32.33 430.39,-26.55 428.72,-33.35"/>
<text text-anchor="middle" x="193.79" y="-5.43" font-family="Times,serif" font-size="14.00">00&#45;&#39;`&#39;,&#39;c&#39;&#45;7f</text>
</g>
<!-- z2&#45;&gt;z2 -->
<g id="edge8" class="edge">
<title>z2&#45;&gt;z2</title>
<path fill="none" stroke="black" d="M271.62,-115.74C268.03,-127.76 273.42,-139.07 287.79,-139.07 298.01,-139.07 303.69,-133.36 304.83,-125.73"/>
<polygon fill="black" stroke="black" points="308.31,-125.4 303.96,-115.74 301.34,-126 308.31,-125.4"/>
<text text-anchor="middle" x="287.79" y="-142.87" font-family="Times,serif" font-size="14.00">&#39;a&#39;</text>
</g>
<!-- z2&#45;&gt;z3 -->
<g id="edge7" class="edge">
<title>z2&#45;&gt;z3</title>
<path fill="none" stroke="black" d="M312.23,-88.03C338.67,-79.41 382.93,-64.83 420.79,-51.63 423.88,-50.55 427.1,-49.41 430.3,-48.26"/>
<polygon fill="black" stroke="black" points="431.51,-51.55 439.72,-44.85 429.12,-44.96 431.51,-51.55"/>
<text text-anchor="middle" x="382.29" y="-80.43" font-family="Times,serif" font-size="14.00">00&#45;&#39;`&#39;,&#39;b&#39;&#45;7f</text>
</g>
<!-- z1&#45;&gt;z2 -->
<g id="edge5" class="edge">
<title>z1&#45;&gt;z2</title>
<path fill="none" stroke="black" d="M137.11,-46.16C165.72,-55.61 219.01,-73.23 253.68,-84.68"/>
<polygon fill="black" stroke="black" points="252.77,-88.07 263.36,-87.88 254.96,-81.42 252.77,-88.07"/>
<text text-anchor="middle" x="193.79" y="-79.43" font-family="Times,serif" font-size="14.00">&#39;b&#39;</text>
</g>
<!-- z1&#45;&gt;z1 -->
<g id="edge4" class="edge">
<title>z1&#45;&gt;z1</title>
<path fill="none" stroke="black" d="M108.02,-59.52C106.74,-69.8 109.52,-79.07 116.34,-79.07 120.72,-79.07 123.43,-75.27 124.48,-69.83"/>
<polygon fill="black" stroke="black" points="127.98,-69.58 124.67,-59.52 120.98,-69.45 127.98,-69.58"/>
<text text-anchor="middle" x="116.34" y="-82.87" font-family="Times,serif" font-size="14.00">&#39;a&#39;</text>
</g>
<!-- z1&#45;&gt;z3 -->
<g id="edge6" class="edge">
<title>z1&#45;&gt;z3</title>
<path fill="none" stroke="black" d="M137.96,-39.51C195.79,-39.17 359.79,-38.21 428.43,-37.81"/>
<polygon fill="black" stroke="black" points="428.62,-41.31 438.6,-37.75 428.58,-34.31 428.62,-41.31"/>
<text text-anchor="middle" x="287.79" y="-42.43" font-family="Times,serif" font-size="14.00">00&#45;&#39;`&#39;,&#39;c&#39;&#45;7f</text>
</g>
<!-- z3&#45;&gt;z3 -->
<g id="edge9" class="edge">
<title>z3&#45;&gt;z3</title>
<path fill="none" stroke="black" d="M447.12,-55.07C443.69,-66.28 448.07,-77.07 460.24,-77.07 468.61,-77.07 473.29,-71.97 474.29,-65.14"/>
<polygon fill="black" stroke="black" points="477.76,-64.71 473.36,-55.07 470.79,-65.35 477.76,-64.71"/>
<text text-anchor="middle" x="460.24" y="-80.87" font-family="Times,serif" font-size="14.00">00&#45;7f</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB