diff options
-rw-r--r-- | examples/parselog.rs | 12 | ||||
-rw-r--r-- | src/lib.rs | 128 | ||||
-rw-r--r-- | src/params.rs | 142 | ||||
-rw-r--r-- | src/table.rs | 12 |
4 files changed, 225 insertions, 69 deletions
diff --git a/examples/parselog.rs b/examples/parselog.rs index c310182..dfd0aee 100644 --- a/examples/parselog.rs +++ b/examples/parselog.rs @@ -1,12 +1,12 @@ //! Parse input from stdin and log actions on stdout use std::io::{self, Read}; -use vte; +use vte::{Params, Parser, Perform}; /// A type implementing Perform that just logs actions struct Log; -impl vte::Perform for Log { +impl Perform for Log { fn print(&mut self, c: char) { println!("[print] {:?}", c); } @@ -15,7 +15,7 @@ impl vte::Perform for Log { println!("[execute] {:02x}", byte); } - fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { println!( "[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", params, intermediates, ignore, c @@ -34,9 +34,9 @@ impl vte::Perform for Log { println!("[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated); } - fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { println!( - "[csi_dispatch] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", + "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", params, intermediates, ignore, c ); } @@ -53,7 +53,7 @@ fn main() { let input = io::stdin(); let mut handle = input.lock(); - let mut statemachine = vte::Parser::new(); + let mut statemachine = Parser::new(); let mut performer = Log; let mut buf = [0; 2048]; @@ -41,14 +41,17 @@ use arrayvec::ArrayVec; use utf8parse as utf8; mod definitions; +mod params; mod table; +pub use params::{Params, ParamsIter}; + use definitions::{unpack, Action, State}; const MAX_INTERMEDIATES: usize = 2; +const MAX_OSC_PARAMS: usize = 16; #[cfg(any(feature = "no_std", test))] const MAX_OSC_RAW: usize = 1024; -const MAX_PARAMS: usize = 16; struct VtUtf8Receiver<'a, P: Perform>(&'a mut P, &'a mut State); @@ -72,14 +75,13 @@ pub struct Parser { state: State, intermediates: [u8; MAX_INTERMEDIATES], intermediate_idx: usize, - params: [i64; MAX_PARAMS], + params: Params, param: i64, - num_params: usize, #[cfg(feature = "no_std")] osc_raw: ArrayVec<[u8; MAX_OSC_RAW]>, #[cfg(not(feature = "no_std"))] osc_raw: Vec<u8>, - osc_params: [(usize, usize); MAX_PARAMS], + osc_params: [(usize, usize); MAX_OSC_PARAMS], osc_num_params: usize, ignoring: bool, utf8_parser: utf8::Parser, @@ -92,8 +94,8 @@ impl Parser { } #[inline] - fn params(&self) -> &[i64] { - &self.params[..self.num_params] + fn params(&self) -> &Params { + &self.params } #[inline] @@ -198,7 +200,7 @@ impl Parser { /// The aliasing is needed here for multiple slices into self.osc_raw #[inline] fn osc_dispatch<P: Perform>(&self, performer: &mut P, byte: u8) { - let mut slices: [MaybeUninit<&[u8]>; MAX_PARAMS] = + let mut slices: [MaybeUninit<&[u8]>; MAX_OSC_PARAMS] = unsafe { MaybeUninit::uninit().assume_init() }; for (i, slice) in slices.iter_mut().enumerate().take(self.osc_num_params) { @@ -219,11 +221,10 @@ impl Parser { Action::Print => performer.print(byte as char), Action::Execute => performer.execute(byte), Action::Hook => { - if self.num_params == MAX_PARAMS { + if self.params.is_full() { self.ignoring = true; } else { - self.params[self.num_params] = self.param; - self.num_params += 1; + self.params.push(self.param); } performer.hook(self.params(), self.intermediates(), self.ignoring, byte as char); @@ -247,8 +248,8 @@ impl Parser { if byte == b';' { let param_idx = self.osc_num_params; match param_idx { - // Only process up to MAX_PARAMS - MAX_PARAMS => return, + // Only process up to MAX_OSC_PARAMS + MAX_OSC_PARAMS => return, // First param is special - 0 to current byte index 0 => { @@ -274,7 +275,7 @@ impl Parser { match param_idx { // Finish last parameter if not already maxed - MAX_PARAMS => (), + MAX_OSC_PARAMS => (), // First param is special - 0 to current byte index 0 => { @@ -294,11 +295,10 @@ impl Parser { }, Action::Unhook => performer.unhook(), Action::CsiDispatch => { - if self.num_params == MAX_PARAMS { + if self.params.is_full() { self.ignoring = true; } else { - self.params[self.num_params] = self.param; - self.num_params += 1; + self.params.push(self.param); } performer.csi_dispatch( @@ -320,18 +320,17 @@ impl Parser { } }, Action::Param => { - // Completed a param - let idx = self.num_params; - - if idx == MAX_PARAMS { + if self.params.is_full() { self.ignoring = true; return; } if byte == b';' { - self.params[idx] = self.param; + self.params.push(self.param); + self.param = 0; + } else if byte == b':' { + self.params.extend(self.param); self.param = 0; - self.num_params += 1; } else { // Continue collecting bytes into param self.param = self.param.saturating_mul(10); @@ -342,8 +341,9 @@ impl Parser { // Reset everything on ESC/CSI/DCS entry self.intermediate_idx = 0; self.ignoring = false; - self.num_params = 0; self.param = 0; + + self.params.clear(); }, Action::BeginUtf8 => self.process_utf8(performer, byte), Action::Ignore => (), @@ -378,7 +378,7 @@ pub trait Perform { /// /// The `ignore` flag indicates that more than two intermediates arrived and /// subsequent characters were ignored. - fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, action: char); + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: char); /// Pass bytes as part of a device control string to the handle chosen in `hook`. C0 controls /// will also be passed to the handler. @@ -398,7 +398,7 @@ pub trait Perform { /// The `ignore` flag indicates that either more than two intermediates arrived /// or the number of parameters exceeded the maximum supported length, /// and subsequent characters were ignored. - fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, action: char); + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: char); /// The final character of an escape sequence has arrived. /// @@ -439,7 +439,7 @@ mod tests { fn execute(&mut self, _: u8) {} - fn hook(&mut self, _: &[i64], _: &[u8], _: bool, _: char) {} + fn hook(&mut self, _: &Params, _: &[u8], _: bool, _: char) {} fn put(&mut self, _: u8) {} @@ -452,7 +452,7 @@ mod tests { self.params = params.iter().map(|p| p.to_vec()).collect(); } - fn csi_dispatch(&mut self, _: &[i64], _: &[u8], _: bool, _: char) {} + fn csi_dispatch(&mut self, _: &Params, _: &[u8], _: bool, _: char) {} fn esc_dispatch(&mut self, _: &[u8], _: bool, _: u8) {} } @@ -461,7 +461,7 @@ mod tests { struct CsiDispatcher { dispatched_csi: bool, ignore: bool, - params: Vec<i64>, + params: Vec<Vec<i64>>, intermediates: Vec<u8>, } @@ -470,7 +470,7 @@ mod tests { fn execute(&mut self, _: u8) {} - fn hook(&mut self, _: &[i64], _: &[u8], _: bool, _: char) {} + fn hook(&mut self, _: &Params, _: &[u8], _: bool, _: char) {} fn put(&mut self, _: u8) {} @@ -478,11 +478,11 @@ mod tests { fn osc_dispatch(&mut self, _: &[&[u8]], _: bool) {} - fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, _: char) { + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, _: char) { self.intermediates = intermediates.to_vec(); - self.params = params.to_vec(); - self.ignore = ignore; self.dispatched_csi = true; + self.params = params.iter().map(|subparam| subparam.to_vec()).collect(); + self.ignore = ignore; } fn esc_dispatch(&mut self, _: &[u8], _: bool, _: u8) {} @@ -503,9 +503,9 @@ mod tests { fn execute(&mut self, _: u8) {} - fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { self.intermediates = intermediates.to_vec(); - self.params = params.to_vec(); + self.params = params.iter().map(|x| x.to_vec()).flatten().collect(); self.ignore = ignore; self.c = Some(c); self.dispatched_dcs = true; @@ -521,7 +521,7 @@ mod tests { fn osc_dispatch(&mut self, _: &[&[u8]], _: bool) {} - fn csi_dispatch(&mut self, _: &[i64], _: &[u8], _: bool, _: char) {} + fn csi_dispatch(&mut self, _: &Params, _: &[u8], _: bool, _: char) {} fn esc_dispatch(&mut self, _: &[u8], _: bool, _: u8) {} } @@ -539,7 +539,7 @@ mod tests { fn execute(&mut self, _: u8) {} - fn hook(&mut self, _: &[i64], _: &[u8], _: bool, _: char) {} + fn hook(&mut self, _: &Params, _: &[u8], _: bool, _: char) {} fn put(&mut self, _: u8) {} @@ -547,7 +547,7 @@ mod tests { fn osc_dispatch(&mut self, _: &[&[u8]], _: bool) {} - fn csi_dispatch(&mut self, _: &[i64], _: &[u8], _: bool, _: char) {} + fn csi_dispatch(&mut self, _: &Params, _: &[u8], _: bool, _: char) {} fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { self.intermediates = intermediates.to_vec(); @@ -588,17 +588,18 @@ mod tests { #[test] fn parse_osc_max_params() { - static INPUT: &[u8] = b"\x1b];;;;;;;;;;;;;;;;;\x1b"; + let params = std::iter::repeat(";").take(params::MAX_PARAMS + 1).collect::<String>(); + let input = format!("\x1b]{}\x1b", ¶ms[..]).into_bytes(); let mut dispatcher = OscDispatcher::default(); let mut parser = Parser::new(); - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); + for byte in input { + parser.advance(&mut dispatcher, byte); } // Check that flag is set and thus osc_dispatch assertions ran. assert!(dispatcher.dispatched_osc); - assert_eq!(dispatcher.params.len(), MAX_PARAMS); + assert_eq!(dispatcher.params.len(), MAX_OSC_PARAMS); for param in dispatcher.params.iter() { assert_eq!(param.len(), 0); } @@ -606,18 +607,19 @@ mod tests { #[test] fn parse_dcs_max_params() { - static INPUT: &[u8] = b"\x1bP1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;p\x1b"; + let params = std::iter::repeat("1;").take(params::MAX_PARAMS + 1).collect::<String>(); + let input = format!("\x1bP{}p", ¶ms[..]).into_bytes(); let mut dispatcher = DcsDispatcher::default(); let mut parser = Parser::new(); - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); + for byte in input { + parser.advance(&mut dispatcher, byte); } // Check that flag is set and thus osc_dispatch assertions ran. assert!(dispatcher.ignore); assert!(dispatcher.dispatched_dcs); - assert_eq!(dispatcher.params.len(), MAX_PARAMS); + assert_eq!(dispatcher.params.len(), params::MAX_PARAMS); for param in dispatcher.params.iter() { assert_eq!(*param, 1); } @@ -656,7 +658,7 @@ mod tests { // This will build a list of repeating '1;'s // The length is MAX_PARAMS - 1 because the last semicolon is interpreted // as an implicit zero, making the total number of parameters MAX_PARAMS - let params = std::iter::repeat("1;").take(MAX_PARAMS - 1).collect::<String>(); + let params = std::iter::repeat("1;").take(params::MAX_PARAMS - 1).collect::<String>(); let input = format!("\x1b[{}p", ¶ms[..]).into_bytes(); let mut dispatcher = CsiDispatcher::default(); @@ -668,7 +670,7 @@ mod tests { // Check that flag is set and thus csi_dispatch assertions ran. assert!(dispatcher.dispatched_csi); - assert_eq!(dispatcher.params.len(), MAX_PARAMS); + assert_eq!(dispatcher.params.len(), params::MAX_PARAMS); assert!(!dispatcher.ignore); } @@ -677,7 +679,7 @@ mod tests { // This will build a list of repeating '1;'s // The length is MAX_PARAMS because the last semicolon is interpreted // as an implicit zero, making the total number of parameters MAX_PARAMS + 1 - let params = std::iter::repeat("1;").take(MAX_PARAMS).collect::<String>(); + let params = std::iter::repeat("1;").take(params::MAX_PARAMS).collect::<String>(); let input = format!("\x1b[{}p", ¶ms[..]).into_bytes(); let mut dispatcher = CsiDispatcher::default(); @@ -689,7 +691,7 @@ mod tests { // Check that flag is set and thus csi_dispatch assertions ran. assert!(dispatcher.dispatched_csi); - assert_eq!(dispatcher.params.len(), MAX_PARAMS); + assert_eq!(dispatcher.params.len(), params::MAX_PARAMS); assert!(dispatcher.ignore); } @@ -702,7 +704,7 @@ mod tests { parser.advance(&mut dispatcher, *byte); } - assert_eq!(dispatcher.params, &[4, 0]); + assert_eq!(dispatcher.params, &[[4], [0]]); } #[test] @@ -716,7 +718,7 @@ mod tests { } // Check that flag is set and thus osc_dispatch assertions ran. - assert_eq!(dispatcher.params, &[0, 4]); + assert_eq!(dispatcher.params, &[[0], [4]]); } #[test] @@ -730,7 +732,7 @@ mod tests { parser.advance(&mut dispatcher, *byte); } - assert_eq!(dispatcher.params, &[i64::MAX as i64]); + assert_eq!(dispatcher.params, &[[i64::MAX as i64]]); } #[test] @@ -746,7 +748,23 @@ mod tests { assert!(dispatcher.dispatched_csi); assert!(!dispatcher.ignore); assert_eq!(dispatcher.intermediates, &[b'?']); - assert_eq!(dispatcher.params, &[1049]); + assert_eq!(dispatcher.params, &[[1049]]); + } + + #[test] + fn csi_subparameters() { + static INPUT: &[u8] = b"\x1b[38:2:255:0:255;1m"; + let mut dispatcher = CsiDispatcher::default(); + let mut parser = Parser::new(); + + for byte in INPUT { + parser.advance(&mut dispatcher, *byte); + } + + assert!(dispatcher.dispatched_csi); + assert!(!dispatcher.ignore); + assert_eq!(dispatcher.intermediates, &[]); + assert_eq!(dispatcher.params, &[vec![38, 2, 255, 0, 255], vec![1]]); } #[test] @@ -887,7 +905,7 @@ mod bench { black_box(byte); } - fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { black_box((params, intermediates, ignore, c)); } @@ -901,7 +919,7 @@ mod bench { black_box((params, bell_terminated)); } - fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { black_box((params, intermediates, ignore, c)); } diff --git a/src/params.rs b/src/params.rs new file mode 100644 index 0000000..efa24cb --- /dev/null +++ b/src/params.rs @@ -0,0 +1,142 @@ +//! Fixed size parameters list with optional subparameters. + +use core::fmt::{self, Debug, Formatter}; + +pub(crate) const MAX_PARAMS: usize = 32; + +#[derive(Default)] +pub struct Params { + /// Number of subparameters for each parameter. + /// + /// For each entry in the `params` slice, this stores the length of the param as number of + /// subparams at the same index as the param in the `params` slice. + /// + /// At the subparam positions the length will always be `0`. + subparams: [u8; MAX_PARAMS], + + /// All parameters and subparameters. + params: [i64; MAX_PARAMS], + + /// Number of suparameters in the current parameter. + current_subparams: u8, + + /// Total number of parameters and subparameters. + len: usize, +} + +impl Params { + /// Returns the number of parameters. + #[inline] + pub fn len(&self) -> usize { + self.len + } + + /// Returns `true` if there are no parameters present. + #[inline] + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns an iterator over all parameters and subparameters. + #[inline] + pub fn iter(&self) -> ParamsIter<'_> { + ParamsIter::new(self) + } + + /// Returns `true` if there is no more space for additional parameters. + #[inline] + pub(crate) fn is_full(&self) -> bool { + self.len == MAX_PARAMS + } + + /// Clear all parameters. + #[inline] + pub(crate) fn clear(&mut self) { + self.current_subparams = 0; + self.len = 0; + } + + /// Add an additional parameter. + #[inline] + pub(crate) fn push(&mut self, item: i64) { + self.subparams[self.len - self.current_subparams as usize] = self.current_subparams + 1; + self.params[self.len] = item; + self.current_subparams = 0; + self.len += 1; + } + + /// Add an additional subparameter to the current parameter. + #[inline] + pub(crate) fn extend(&mut self, item: i64) { + self.params[self.len] = item; + self.current_subparams += 1; + self.len += 1; + } +} + +impl<'a> IntoIterator for &'a Params { + type IntoIter = ParamsIter<'a>; + type Item = &'a [i64]; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// Immutable subparameter iterator. +pub struct ParamsIter<'a> { + params: &'a Params, + index: usize, +} + +impl<'a> ParamsIter<'a> { + fn new(params: &'a Params) -> Self { + Self { params, index: 0 } + } +} + +impl<'a> Iterator for ParamsIter<'a> { + type Item = &'a [i64]; + + fn next(&mut self) -> Option<Self::Item> { + if self.index >= self.params.len() { + return None; + } + + // Get all subparameters for the current parameter. + let num_subparams = self.params.subparams[self.index]; + let param = &self.params.params[self.index..self.index + num_subparams as usize]; + + // Jump to the next parameter. + self.index += num_subparams as usize; + + Some(param) + } + + fn size_hint(&self) -> (usize, Option<usize>) { + let remaining = self.params.len() - self.index; + (remaining, Some(remaining)) + } +} + +impl Debug for Params { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "[")?; + + for (i, param) in self.iter().enumerate() { + if i != 0 { + write!(f, ";")?; + } + + for (i, subparam) in param.iter().enumerate() { + if i != 0 { + write!(f, ":")?; + } + + subparam.fmt(f)?; + } + } + + write!(f, "]") + } +} diff --git a/src/table.rs b/src/table.rs index c19e4ab..f2c0105 100644 --- a/src/table.rs +++ b/src/table.rs @@ -64,9 +64,8 @@ generate_state_changes!(state_changes, { 0x1c..=0x1f => (Anywhere, Execute), 0x7f => (Anywhere, Ignore), 0x20..=0x2f => (CsiIntermediate, Collect), - 0x3a => (CsiIgnore, None), 0x30..=0x39 => (CsiParam, Param), - 0x3b => (CsiParam, Param), + 0x3a..=0x3b => (CsiParam, Param), 0x3c..=0x3f => (CsiParam, Collect), 0x40..=0x7e => (Ground, CsiDispatch), }, @@ -85,9 +84,8 @@ generate_state_changes!(state_changes, { 0x19 => (Anywhere, Execute), 0x1c..=0x1f => (Anywhere, Execute), 0x30..=0x39 => (Anywhere, Param), - 0x3b => (Anywhere, Param), + 0x3a..=0x3b => (Anywhere, Param), 0x7f => (Anywhere, Ignore), - 0x3a => (CsiIgnore, None), 0x3c..=0x3f => (CsiIgnore, None), 0x20..=0x2f => (CsiIntermediate, Collect), 0x40..=0x7e => (Ground, CsiDispatch), @@ -108,10 +106,9 @@ generate_state_changes!(state_changes, { 0x19 => (Anywhere, Ignore), 0x1c..=0x1f => (Anywhere, Ignore), 0x7f => (Anywhere, Ignore), - 0x3a => (DcsIgnore, None), 0x20..=0x2f => (DcsIntermediate, Collect), 0x30..=0x39 => (DcsParam, Param), - 0x3b => (DcsParam, Param), + 0x3a..=0x3b => (DcsParam, Param), 0x3c..=0x3f => (DcsParam, Collect), 0x40..=0x7e => (DcsPassthrough, None), }, @@ -139,9 +136,8 @@ generate_state_changes!(state_changes, { 0x19 => (Anywhere, Ignore), 0x1c..=0x1f => (Anywhere, Ignore), 0x30..=0x39 => (Anywhere, Param), - 0x3b => (Anywhere, Param), + 0x3a..=0x3b => (Anywhere, Param), 0x7f => (Anywhere, Ignore), - 0x3a => (DcsIgnore, None), 0x3c..=0x3f => (DcsIgnore, None), 0x20..=0x2f => (DcsIntermediate, Collect), 0x40..=0x7e => (DcsPassthrough, None), |