diff --git a/src/configotron/fsm/display.rs b/src/configotron/fsm/display.rs index e69de29..3487443 100644 --- a/src/configotron/fsm/display.rs +++ b/src/configotron/fsm/display.rs @@ -0,0 +1,114 @@ +use crate::configotron::fsm::TransitionMask; + + + +pub fn mask_to_string(mask: TransitionMask) -> String { + let mut last_bit: bool = false; + let mut range_start: Option = None; + let mut parts: Vec = Vec::new(); + + fn num_to_string(i: u8) -> String { + match i { + 0x00 => "NUL".to_string(), + 0x09 => "TAB".to_string(), + 0x1B => "ESC".to_string(), + 0x20 => "SP".to_string(), + 0x7F => "DEL".to_string(), + 0x21..=0x7E => format!("\'{}\'", (i as char).to_string()), + _ => format!("0x{:02X}", i), + } + } + + for i in 0..=0x7F { + let current_bit = (mask & (1 << i)) > 0; + + match (current_bit, last_bit, range_start) { + (true, false, None) => { + // Start of a new range + range_start = Some(i); + last_bit = true; + } + (true, true, Some(_)) => { + // Continue current range + last_bit = true; + } + (false, true, Some(start)) => { + // End of a range + if start == i - 1 { + parts.push(num_to_string(start as u8)); + } else if start == i - 2 { + parts.push(num_to_string(start as u8)); + parts.push(num_to_string((i - 1) as u8)); + } else { + parts.push(format!("{}..{}", num_to_string(start as u8), num_to_string((i - 1) as u8))); + } + range_start = None; + last_bit = false; + } + (false, false, None) => { + // Nothing to do + last_bit = false; + } + _ => {} + } + } + + match (last_bit, range_start) { + (_, None) => {}, + (_, Some(start)) => { + if start == 0x7f { + parts.push(num_to_string(start as u8)); + } else if start == 0x7e { + parts.push(num_to_string(start as u8)); + parts.push(num_to_string((0x7f) as u8)); + } else { + parts.push(format!("{}..{}", num_to_string(start as u8), num_to_string((0x7f - 1) as u8))); + } + } + } + + parts.join(",") + +} + +#[cfg(test)] +mod test { + use crate::configotron::fsm::{display::mask_to_string, masks::{mask_all, mask_char, mask_char_range}}; + + #[test] + fn mask_to_string_test() { + mask_to_string(mask_char('\x7F')); + assert_eq!(mask_to_string(mask_char('A')), "'A'".to_string()); + assert_eq!(mask_to_string(mask_all()), "NUL..'~'".to_string()); + assert_eq!(mask_to_string(mask_char('z')), "'z'".to_string()); + assert_eq!(mask_to_string(mask_char('0')), "'0'".to_string()); + assert_eq!(mask_to_string(mask_char_range('0', '9')), "'0'..'9'".to_string()); + assert_eq!(mask_to_string(mask_char_range('x', 'z')), "'x'..'z'".to_string()); + assert_eq!(mask_to_string(mask_char('a') | mask_char('c')), "'a','c'".to_string()); + assert_eq!(mask_to_string(mask_char('a') | mask_char('b') | mask_char('c')), "'a'..'c'".to_string()); + assert_eq!(mask_to_string(mask_char_range('a', 'c') | mask_char('e')), "'a'..'c','e'".to_string()); + assert_eq!(mask_to_string(mask_char('\t')), "TAB".to_string()); + assert_eq!(mask_to_string(mask_char('\x7F')), "DEL".to_string()); + assert_eq!(mask_to_string(mask_char('\x00')), "NUL".to_string()); + assert_eq!(mask_to_string(mask_char_range('A', 'A')), "'A'".to_string()); + assert_eq!(mask_to_string(mask_char(' ')), "SP".to_string()); + assert_eq!(mask_to_string(mask_char('\x1B')), "ESC".to_string()); + assert_eq!( + mask_to_string( + mask_char('a') + | mask_char('c') + | mask_char_range('e', 'g') + | mask_char('i') + | mask_char_range('k', 'm') + | mask_char('o') + | mask_char('q') + | mask_char('s') + | mask_char('u') + | mask_char('w') + | mask_char('y') + | mask_char('z') + ), + "'a','c','e'..'g','i','k'..'m','o','q','s','u','w','y','z'".to_string() + ); + } +} \ No newline at end of file diff --git a/src/configotron/fsm/masks.rs b/src/configotron/fsm/masks.rs index ec6b0a1..0b519b0 100644 --- a/src/configotron/fsm/masks.rs +++ b/src/configotron/fsm/masks.rs @@ -36,7 +36,7 @@ pub fn mask_number_range(start: u8, end: u8) -> TransitionMask { mask } -pub fn mask_number_char(c: char) -> TransitionMask { +pub fn mask_char(c: char) -> TransitionMask { if c.is_ascii() && (c as u8) <= 0x7F { 1 << (c as u8) } else { @@ -48,6 +48,18 @@ pub fn mask_all() -> TransitionMask { u128::MAX } +pub fn mask_char_range(start: char, end: char) -> TransitionMask { + let mut mask: TransitionMask = 0; + let start_u8 = start as u32; + let end_u8 = end as u32; + for c in start_u8..=end_u8 { + if c <= 0x7F { + mask |= 1 << (c as u8); + } + } + mask +} + #[cfg(test)] mod test { use crate::configotron::fsm::masks::decompose_transition; diff --git a/src/configotron/fsm/mod.rs b/src/configotron/fsm/mod.rs index 147ae32..39084ad 100644 --- a/src/configotron/fsm/mod.rs +++ b/src/configotron/fsm/mod.rs @@ -155,7 +155,7 @@ pub fn run_fsm(fsm: &FiniteStateMachine, input: &String) -> StateType { #[cfg(test)] mod test { - use crate::configotron::fsm::{masks::{mask_all, mask_number_char}, run_fsm, Action, FSMBuilder, StateType}; + use crate::configotron::fsm::{masks::{mask_all, mask_char}, run_fsm, Action, FSMBuilder, StateType}; #[test] pub fn basic_fsm_build() { @@ -181,13 +181,13 @@ mod test { // u5: * -> u5 // Error/Default: z3 - fsm_builder.add_link(&z0, &z1, Action::None, mask_number_char('a')).unwrap(); - fsm_builder.add_link(&z0, &z2, Action::None, mask_number_char('b')).unwrap(); + 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_number_char('a')).unwrap(); - fsm_builder.add_link(&z1, &z2, Action::None, mask_number_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_number_char('a')).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());