From 1af8b6ed51bb48ea998fc79677fbd4f215da1843 Mon Sep 17 00:00:00 2001 From: John Toohey Date: Sun, 21 Jan 2024 15:47:58 +0000 Subject: [PATCH 1/8] Adds support for APC (Application Program Commands) within the application Several changes were made for this: - `STATE_CHANGES` now stores a tuple of `(State, Action)` as opposed to `u8` - The negative aspect of this is that it will now take over twice as much space in memory (4 KiB) to 8.5 KiB - On a modern system this should not come at a reasonable preformance cost - The justification for this is that further `Action`s were needed to help manage an APC command: `ApcBegin`, `ApcEnd`, and `ApcPut`. Therein it became necassary to extend the struct from a quasi-`u4` to a `u8` as these represent values 16, 17, and 18 respectively. - This also removes the functions `pack` and `unpack`, a possible benefit of the change. - This means code was also changed in the macro library `vte_generate_state_changes` - `Perform` now includes three new functions with signatures listed below: fn apc_begin(&mut self) {} fn apc_end(&mut self) {} fn apc_put(&mut self) {} These I believe are the best way to facilitate the use of APC. An alternative method could exist without `apc_begin`, although I believe that from a library perspective it is more ergonomic to have such as function, as it elimintates a potential check required by downstream libraries. - For illustration purposes there is a new examples `apc.rs`. I will delete this example in the next commit and merge its functionality with the already existing `parselog.rs` and the testing suite. I have merely kept it to help demonstrate the functionality provided in the commit, and to facilitate my preliminary testing of it --- examples/apc.rs | 76 ++++++++++++++++++++++++++ examples/apc.vte | 3 ++ src/definitions.rs | 77 +++------------------------ src/lib.rs | 18 +++++-- src/table.rs | 28 ++++++---- vte_generate_state_changes/src/lib.rs | 7 ++- 6 files changed, 122 insertions(+), 87 deletions(-) create mode 100644 examples/apc.rs create mode 100644 examples/apc.vte diff --git a/examples/apc.rs b/examples/apc.rs new file mode 100644 index 0000000..855d624 --- /dev/null +++ b/examples/apc.rs @@ -0,0 +1,76 @@ +//! Parse input from stdin and log actions on stdout +use std::io::{self, Read}; + +use vte::{Params, Parser, Perform}; + +/// A type implementing Perform that just logs actions +struct Log; + +impl Perform for Log { + fn print(&mut self, c: char) { + println!("[print] {:?}", c); + } + + fn execute(&mut self, byte: u8) { + println!("[execute] {:02x}", byte); + } + + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { + println!( + "[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", + params, intermediates, ignore, c + ); + } + + fn put(&mut self, byte: u8) { + println!("[put] {:02x}", byte); + } + + fn unhook(&mut self) { + println!("[unhook]"); + } + + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + println!("[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated); + } + + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { + println!( + "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", + params, intermediates, ignore, c + ); + for param in params.iter() { + dbg!(param); + } + } + + fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { + println!( + "[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}", + intermediates, ignore, byte + ); + } + + fn apc_begin(&mut self) { + println!("[apc_begin]"); + } + + fn apc_end(&mut self) { + println!("[apc_end]"); + } + + fn apc_put(&mut self, byte: u8) { + println!("[apc_end] {:?}", byte as char); + } +} + +fn main() { + let mut statemachine = Parser::new(); + let mut performer = Log; + + let buf = include_bytes!("apc.vte"); + + for byte in buf { + statemachine.advance(&mut performer, *byte); + } +} diff --git a/examples/apc.vte b/examples/apc.vte new file mode 100644 index 0000000..c71fd78 --- /dev/null +++ b/examples/apc.vte @@ -0,0 +1,3 @@ +We shall now show an image: + _Ga=T,q=2,f=24,t=s,s=387,v=480,X=2;a2l0dHktaWNhdC1HWFVKUjVDTFI2MkdR\ +The Scream, Edvard Munch diff --git a/src/definitions.rs b/src/definitions.rs index 218c1eb..e16e061 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -1,8 +1,6 @@ -use core::mem; - #[allow(dead_code)] #[repr(u8)] -#[derive(Debug, Default, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] pub enum State { Anywhere = 0, CsiEntry = 1, @@ -19,13 +17,14 @@ pub enum State { #[default] Ground = 12, OscString = 13, - SosPmApcString = 14, + SosPmString = 14, Utf8 = 15, + ApcString = 16, } #[allow(dead_code)] #[repr(u8)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Action { None = 0, Clear = 1, @@ -43,69 +42,7 @@ pub enum Action { Put = 13, Unhook = 14, BeginUtf8 = 15, -} - -/// Unpack a u8 into a State and Action -/// -/// The implementation of this assumes that there are *precisely* 16 variants for both Action and -/// State. Furthermore, it assumes that the enums are tag-only; that is, there is no data in any -/// variant. -/// -/// Bad things will happen if those invariants are violated. -#[inline(always)] -pub fn unpack(delta: u8) -> (State, Action) { - unsafe { - ( - // State is stored in bottom 4 bits - mem::transmute(delta & 0x0f), - // Action is stored in top 4 bits - mem::transmute(delta >> 4), - ) - } -} - -#[inline(always)] -pub const fn pack(state: State, action: Action) -> u8 { - (action as u8) << 4 | state as u8 -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn unpack_state_action() { - match unpack(0xee) { - (State::SosPmApcString, Action::Unhook) => (), - _ => panic!("unpack failed"), - } - - match unpack(0x0f) { - (State::Utf8, Action::None) => (), - _ => panic!("unpack failed"), - } - - match unpack(0xff) { - (State::Utf8, Action::BeginUtf8) => (), - _ => panic!("unpack failed"), - } - } - - #[test] - fn pack_state_action() { - match unpack(0xee) { - (State::SosPmApcString, Action::Unhook) => (), - _ => panic!("unpack failed"), - } - - match unpack(0x0f) { - (State::Utf8, Action::None) => (), - _ => panic!("unpack failed"), - } - - match unpack(0xff) { - (State::Utf8, Action::BeginUtf8) => (), - _ => panic!("unpack failed"), - } - } + ApcBegin = 16, + ApcEnd = 17, + ApcPut = 18, } diff --git a/src/lib.rs b/src/lib.rs index 31e2a31..0f5d54e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ mod table; pub mod ansi; pub use params::{Params, ParamsIter}; -use definitions::{unpack, Action, State}; +use definitions::{Action, State}; const MAX_INTERMEDIATES: usize = 2; const MAX_OSC_PARAMS: usize = 16; @@ -138,12 +138,12 @@ impl Parser { // for current state. let mut change = table::STATE_CHANGES[State::Anywhere as usize][byte as usize]; - if change == 0 { + if change == (State::Anywhere, Action::None) { change = table::STATE_CHANGES[self.state as usize][byte as usize]; } // Unpack into a state and action - let (state, action) = unpack(change); + let (state, action) = change; self.perform_state_change(performer, state, action, byte); } @@ -364,6 +364,9 @@ impl Parser { Action::BeginUtf8 => self.process_utf8(performer, byte), Action::Ignore => (), Action::None => (), + Action::ApcBegin => performer.apc_begin(), + Action::ApcEnd => performer.apc_end(), + Action::ApcPut => performer.apc_put(byte), } } } @@ -428,6 +431,15 @@ pub trait Perform { /// The `ignore` flag indicates that more than two intermediates arrived and /// subsequent characters were ignored. fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {} + + /// Called at the start of an APC (application program command) + fn apc_begin(&mut self) {} + + /// Called at the end of an APC (application program command) + fn apc_end(&mut self) {} + + /// A byte within an APC (application program command) byte-string + fn apc_put(&mut self, _byte: u8) {} } #[cfg(all(test, feature = "no_std"))] diff --git a/src/table.rs b/src/table.rs index f2c0105..bf05d7b 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1,11 +1,11 @@ /// This is the state change table. It's indexed first by current state and then by the next /// character in the pty stream. -use crate::definitions::{pack, Action, State}; +use crate::definitions::{Action, State}; use vte_generate_state_changes::generate_state_changes; // Generate state changes at compile-time -pub static STATE_CHANGES: [[u8; 256]; 16] = state_changes(); +pub static STATE_CHANGES: [[(State, Action); 256]; 17] = state_changes(); generate_state_changes!(state_changes, { Anywhere { 0x18 => (Ground, Execute), @@ -44,9 +44,9 @@ generate_state_changes!(state_changes, { 0x5b => (CsiEntry, None), 0x5d => (OscString, None), 0x50 => (DcsEntry, None), - 0x58 => (SosPmApcString, None), - 0x5e => (SosPmApcString, None), - 0x5f => (SosPmApcString, None), + 0x58 => (SosPmString, None), + 0x5e => (SosPmString, None), + 0x5f => (ApcString, ApcBegin), }, EscapeIntermediate { @@ -152,11 +152,11 @@ generate_state_changes!(state_changes, { 0x9c => (Ground, None), }, - SosPmApcString { - 0x00..=0x17 => (Anywhere, Ignore), - 0x19 => (Anywhere, Ignore), - 0x1c..=0x1f => (Anywhere, Ignore), - 0x20..=0x7f => (Anywhere, Ignore), + SosPmString { + 0x00..=0x17 => (SosPmString, Ignore), + 0x19 => (SosPmString, Ignore), + 0x1c..=0x1f => (SosPmString, Ignore), + 0x20..=0x7f => (SosPmString, Ignore), 0x9c => (Ground, None), }, @@ -168,4 +168,12 @@ generate_state_changes!(state_changes, { 0x1c..=0x1f => (Anywhere, Ignore), 0x20..=0xff => (Anywhere, OscPut), } + + ApcString { + 0x00..=0x17 => (ApcString, ApcPut), + 0x19 => (ApcString, ApcPut), + 0x1c..=0x1f => (ApcString, ApcPut), + 0x20..=0x7f => (ApcString, ApcPut), + 0x9c => (Ground, ApcEnd), + }, }); diff --git a/vte_generate_state_changes/src/lib.rs b/vte_generate_state_changes/src/lib.rs index b016518..19ac02f 100644 --- a/vte_generate_state_changes/src/lib.rs +++ b/vte_generate_state_changes/src/lib.rs @@ -25,8 +25,8 @@ pub fn generate_state_changes(item: proc_macro::TokenStream) -> proc_macro::Toke let assignments_stream = states_stream(&mut iter); quote!( - const fn #fn_name() -> [[u8; 256]; 16] { - let mut state_changes = [[0; 256]; 16]; + const fn #fn_name() -> [[(State, Action); 256]; 17] { + let mut state_changes = [[(State::Anywhere, Action::None); 256]; 17]; #assignments_stream @@ -102,10 +102,9 @@ fn change_stream(iter: &mut Peekable, state: &TokenTree) // Create a new entry for every byte in the range for byte in start..=end { // TODO: Force adding `State::` and `Action::`? - // TODO: Should we really use `pack` here without import? tokens.extend(quote!( state_changes[State::#state as usize][#byte] = - pack(State::#target_state, Action::#target_action); + (State::#target_state, Action::#target_action); )); } } From 955d29368766232d3faacaae3ecb6067b7f38f00 Mon Sep 17 00:00:00 2001 From: John Toohey Date: Sun, 21 Jan 2024 17:22:19 +0000 Subject: [PATCH 2/8] Rewrote APC to be more similar to OSC - This involved: - Removing its dedicated (and frankly useless) example - Writing a proper test - Having it store a buffer. It currently uses its own buffer although it could fairly easily be merged with that of OSC without any obvious drawbacks. Next commit? - Rather than having three functions within `Perform` it now has just one `acp_dispatch` which should be easier to code with. --- examples/apc.rs | 76 ------------------------------------------ examples/apc.vte | 3 -- examples/parselog.rs | 4 +++ src/definitions.rs | 2 +- src/lib.rs | 78 +++++++++++++++++++++++++++++++++++++------- src/table.rs | 13 ++++---- 6 files changed, 79 insertions(+), 97 deletions(-) delete mode 100644 examples/apc.rs delete mode 100644 examples/apc.vte diff --git a/examples/apc.rs b/examples/apc.rs deleted file mode 100644 index 855d624..0000000 --- a/examples/apc.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! Parse input from stdin and log actions on stdout -use std::io::{self, Read}; - -use vte::{Params, Parser, Perform}; - -/// A type implementing Perform that just logs actions -struct Log; - -impl Perform for Log { - fn print(&mut self, c: char) { - println!("[print] {:?}", c); - } - - fn execute(&mut self, byte: u8) { - println!("[execute] {:02x}", byte); - } - - fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { - println!( - "[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", - params, intermediates, ignore, c - ); - } - - fn put(&mut self, byte: u8) { - println!("[put] {:02x}", byte); - } - - fn unhook(&mut self) { - println!("[unhook]"); - } - - fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { - println!("[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated); - } - - fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { - println!( - "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", - params, intermediates, ignore, c - ); - for param in params.iter() { - dbg!(param); - } - } - - fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { - println!( - "[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}", - intermediates, ignore, byte - ); - } - - fn apc_begin(&mut self) { - println!("[apc_begin]"); - } - - fn apc_end(&mut self) { - println!("[apc_end]"); - } - - fn apc_put(&mut self, byte: u8) { - println!("[apc_end] {:?}", byte as char); - } -} - -fn main() { - let mut statemachine = Parser::new(); - let mut performer = Log; - - let buf = include_bytes!("apc.vte"); - - for byte in buf { - statemachine.advance(&mut performer, *byte); - } -} diff --git a/examples/apc.vte b/examples/apc.vte deleted file mode 100644 index c71fd78..0000000 --- a/examples/apc.vte +++ /dev/null @@ -1,3 +0,0 @@ -We shall now show an image: - _Ga=T,q=2,f=24,t=s,s=387,v=480,X=2;a2l0dHktaWNhdC1HWFVKUjVDTFI2MkdR\ -The Scream, Edvard Munch diff --git a/examples/parselog.rs b/examples/parselog.rs index dfd0aee..7753bae 100644 --- a/examples/parselog.rs +++ b/examples/parselog.rs @@ -47,6 +47,10 @@ impl Perform for Log { intermediates, ignore, byte ); } + + fn apc_dispatch(&mut self, bytes: &[u8]) { + println!("[apc_dispatch] {:?}", bytes); + } } fn main() { diff --git a/src/definitions.rs b/src/definitions.rs index e16e061..b257b66 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -42,7 +42,7 @@ pub enum Action { Put = 13, Unhook = 14, BeginUtf8 = 15, - ApcBegin = 16, + ApcStart = 16, ApcEnd = 17, ApcPut = 18, } diff --git a/src/lib.rs b/src/lib.rs index 0f5d54e..e656e24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,10 @@ pub struct Parser { osc_raw: Vec, osc_params: [(usize, usize); MAX_OSC_PARAMS], osc_num_params: usize, + #[cfg(feature = "no_std")] + apc_raw: ArrayVec, + #[cfg(not(feature = "no_std"))] + apc_raw: Vec, ignoring: bool, utf8_parser: utf8::Parser, } @@ -187,6 +191,9 @@ impl Parser { State::OscString => { self.perform_action(performer, Action::OscEnd, byte); }, + State::ApcString => { + self.perform_action(performer, Action::ApcEnd, byte); + }, _ => (), } @@ -202,6 +209,9 @@ impl Parser { State::OscString => { self.perform_action(performer, Action::OscStart, byte); }, + State::ApcString => { + self.perform_action(performer, Action::ApcStart, byte); + }, _ => (), } @@ -231,6 +241,10 @@ impl Parser { } } + fn apc_dispatch(&self, performer: &mut P) { + performer.apc_dispatch(&self.apc_raw); + } + #[inline] fn perform_action(&mut self, performer: &mut P, action: Action, byte: u8) { match action { @@ -364,9 +378,22 @@ impl Parser { Action::BeginUtf8 => self.process_utf8(performer, byte), Action::Ignore => (), Action::None => (), - Action::ApcBegin => performer.apc_begin(), - Action::ApcEnd => performer.apc_end(), - Action::ApcPut => performer.apc_put(byte), + Action::ApcStart => { + self.apc_raw.clear(); + }, + Action::ApcEnd => { + self.apc_dispatch(performer); + }, + Action::ApcPut => { + #[cfg(feature = "no_std")] + { + if self.apc_raw.is_full() { + return; + } + } + + self.apc_raw.push(byte); + }, } } } @@ -432,14 +459,9 @@ pub trait Perform { /// subsequent characters were ignored. fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {} - /// Called at the start of an APC (application program command) - fn apc_begin(&mut self) {} - - /// Called at the end of an APC (application program command) - fn apc_end(&mut self) {} - - /// A byte within an APC (application program command) byte-string - fn apc_put(&mut self, _byte: u8) {} + /// Called at the end of an APC (application program command), where + /// `bytes` is the content of the APC + fn apc_dispatch(&mut self, _bytes: &[u8]) {} } #[cfg(all(test, feature = "no_std"))] @@ -471,6 +493,7 @@ mod tests { Esc(Vec, bool, u8), DcsHook(Vec>, Vec, bool, char), DcsPut(u8), + Apc(Vec), DcsUnhook, } @@ -504,6 +527,10 @@ mod tests { fn unhook(&mut self) { self.dispatched.push(Sequence::DcsUnhook); } + + fn apc_dispatch(&mut self, bytes: &[u8]) { + self.dispatched.push(Sequence::Apc(bytes.to_vec())) + } } #[test] @@ -892,6 +919,35 @@ mod tests { assert_eq!(dispatcher.dispatched[6], Sequence::DcsUnhook); } + #[test] + fn parse_apc() { + const INPUT_1: &[u8] = b"\x1b_abc\x9c"; + const INPUT_2: &[u8] = b"\x1b_abc\x1b\\"; + + // Test with ST terminator + + let mut dispatcher = Dispatcher::default(); + let mut parser = Parser::new(); + + for byte in INPUT_1 { + parser.advance(&mut dispatcher, *byte); + } + assert_eq!(dispatcher.dispatched, vec![Sequence::Apc(b"abc".to_vec()),]); + + // Test with ESC \ terminator + + let mut dispatcher = Dispatcher::default(); + let mut parser = Parser::new(); + + for byte in INPUT_2 { + parser.advance(&mut dispatcher, *byte); + } + assert_eq!( + dispatcher.dispatched, + vec![Sequence::Apc(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] + ) + } + #[test] fn intermediate_reset_on_dcs_exit() { static INPUT: &[u8] = b"\x1bP=1sZZZ\x1b+\x5c"; diff --git a/src/table.rs b/src/table.rs index bf05d7b..6b55017 100644 --- a/src/table.rs +++ b/src/table.rs @@ -46,7 +46,7 @@ generate_state_changes!(state_changes, { 0x50 => (DcsEntry, None), 0x58 => (SosPmString, None), 0x5e => (SosPmString, None), - 0x5f => (ApcString, ApcBegin), + 0x5f => (ApcString, None), }, EscapeIntermediate { @@ -170,10 +170,11 @@ generate_state_changes!(state_changes, { } ApcString { - 0x00..=0x17 => (ApcString, ApcPut), - 0x19 => (ApcString, ApcPut), - 0x1c..=0x1f => (ApcString, ApcPut), - 0x20..=0x7f => (ApcString, ApcPut), - 0x9c => (Ground, ApcEnd), + 0x00..=0x06 => (Anywhere, Ignore), + 0x08..=0x17 => (Anywhere, Ignore), + 0x19 => (Anywhere, Ignore), + 0x1c..=0x1f => (Anywhere, Ignore), + 0x20..=0xff => (Anywhere, ApcPut), + 0x9c => (Ground, None), }, }); From b6b8e50ee5f5b3e6da8d61d5ee0869cb9f195354 Mon Sep 17 00:00:00 2001 From: John Toohey Date: Sun, 21 Jan 2024 17:25:55 +0000 Subject: [PATCH 3/8] Merged the OSC and APC buffers --- src/lib.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e656e24..dac32d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,8 @@ impl<'a, P: Perform> utf8::Receiver for VtUtf8Receiver<'a, P> { /// [`Perform`]: trait.Perform.html /// /// Generic over the value for the size of the raw Operating System Command -/// buffer. Only used when the `no_std` feature is enabled. +/// buffer. Only used when the `no_std` feature is enabled. This buffer is +/// also used for APC (Application Program Commands.) #[derive(Default)] pub struct Parser { state: State, @@ -87,10 +88,6 @@ pub struct Parser { osc_raw: Vec, osc_params: [(usize, usize); MAX_OSC_PARAMS], osc_num_params: usize, - #[cfg(feature = "no_std")] - apc_raw: ArrayVec, - #[cfg(not(feature = "no_std"))] - apc_raw: Vec, ignoring: bool, utf8_parser: utf8::Parser, } @@ -242,7 +239,7 @@ impl Parser { } fn apc_dispatch(&self, performer: &mut P) { - performer.apc_dispatch(&self.apc_raw); + performer.apc_dispatch(&self.osc_raw); } #[inline] @@ -379,7 +376,7 @@ impl Parser { Action::Ignore => (), Action::None => (), Action::ApcStart => { - self.apc_raw.clear(); + self.osc_raw.clear(); }, Action::ApcEnd => { self.apc_dispatch(performer); @@ -387,12 +384,12 @@ impl Parser { Action::ApcPut => { #[cfg(feature = "no_std")] { - if self.apc_raw.is_full() { + if self.osc_raw.is_full() { return; } } - self.apc_raw.push(byte); + self.osc_raw.push(byte); }, } } From 946346aa3ea051734a8618e173f6eb7eeaefa06c Mon Sep 17 00:00:00 2001 From: John Toohey Date: Sun, 21 Jan 2024 17:36:21 +0000 Subject: [PATCH 4/8] cargo fmt --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dac32d6..42ae96e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -939,10 +939,10 @@ mod tests { for byte in INPUT_2 { parser.advance(&mut dispatcher, *byte); } - assert_eq!( - dispatcher.dispatched, - vec![Sequence::Apc(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] - ) + assert_eq!(dispatcher.dispatched, vec![ + Sequence::Apc(b"abc".to_vec()), + Sequence::Esc(vec![], false, 92) + ]) } #[test] From e3efc4e0fb3b99e371bea7651fabc091c1c4f90c Mon Sep 17 00:00:00 2001 From: John Toohey Date: Sun, 25 Feb 2024 12:21:02 +0000 Subject: [PATCH 5/8] Added support for APC / SOS / PM messages This has a lower performance overhead. Needs to be tested further. --- src/definitions.rs | 12 ++-- src/lib.rs | 159 ++++++++++++++++++++++++++++++++++++++------- src/table.rs | 16 ++--- 3 files changed, 149 insertions(+), 38 deletions(-) diff --git a/src/definitions.rs b/src/definitions.rs index 218c1eb..6857e2f 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -19,7 +19,7 @@ pub enum State { #[default] Ground = 12, OscString = 13, - SosPmApcString = 14, + Null = 14, Utf8 = 15, } @@ -35,9 +35,9 @@ pub enum Action { Execute = 5, Hook = 6, Ignore = 7, - OscEnd = 8, - OscPut = 9, - OscStart = 10, + StringEnd = 8, + StringPut = 9, + StringStart = 10, Param = 11, Print = 12, Put = 13, @@ -76,7 +76,7 @@ mod tests { #[test] fn unpack_state_action() { match unpack(0xee) { - (State::SosPmApcString, Action::Unhook) => (), + (State::Null, Action::Unhook) => (), _ => panic!("unpack failed"), } @@ -94,7 +94,7 @@ mod tests { #[test] fn pack_state_action() { match unpack(0xee) { - (State::SosPmApcString, Action::Unhook) => (), + (State::Null, Action::Unhook) => (), _ => panic!("unpack failed"), } diff --git a/src/lib.rs b/src/lib.rs index 31e2a31..fc121e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,7 @@ pub struct Parser { osc_num_params: usize, ignoring: bool, utf8_parser: utf8::Parser, + str_start: u8, } impl Parser { @@ -185,7 +186,7 @@ impl Parser { self.perform_action(performer, Action::Unhook, byte); }, State::OscString => { - self.perform_action(performer, Action::OscEnd, byte); + self.perform_action(performer, Action::StringEnd, byte); }, _ => (), } @@ -200,7 +201,7 @@ impl Parser { self.perform_action(performer, Action::Hook, byte); }, State::OscString => { - self.perform_action(performer, Action::OscStart, byte); + self.perform_action(performer, Action::StringStart, byte); }, _ => (), } @@ -231,6 +232,21 @@ impl Parser { } } + #[inline] + fn apc_dispatch(&self, performer: &mut P) { + performer.apc_dispatch(&self.osc_raw); + } + + #[inline] + fn pm_dispatch(&self, performer: &mut P) { + performer.pm_dispatch(&self.osc_raw); + } + + #[inline] + fn sos_dispatch(&self, performer: &mut P) { + performer.sos_dispatch(&self.osc_raw); + } + #[inline] fn perform_action(&mut self, performer: &mut P, action: Action, byte: u8) { match action { @@ -246,11 +262,13 @@ impl Parser { performer.hook(self.params(), self.intermediates(), self.ignoring, byte as char); }, Action::Put => performer.put(byte), - Action::OscStart => { + Action::StringStart => { + self.str_start = byte % 64; self.osc_raw.clear(); self.osc_num_params = 0; }, - Action::OscPut => { + Action::StringPut => { + eprintln!("{:x} {:x}: {:x}", self.str_start, self.str_start % 64, byte); #[cfg(feature = "no_std")] { if self.osc_raw.is_full() { @@ -261,7 +279,7 @@ impl Parser { let idx = self.osc_raw.len(); // Param separator - if byte == b';' { + if self.str_start == 0x1d && byte == b';' { let param_idx = self.osc_num_params; match param_idx { // Only process up to MAX_OSC_PARAMS @@ -285,29 +303,46 @@ impl Parser { self.osc_raw.push(byte); } }, - Action::OscEnd => { + Action::StringEnd => { + eprintln!("str end"); let param_idx = self.osc_num_params; let idx = self.osc_raw.len(); - match param_idx { - // Finish last parameter if not already maxed - MAX_OSC_PARAMS => (), - - // First param is special - 0 to current byte index - 0 => { - self.osc_params[param_idx] = (0, idx); - self.osc_num_params += 1; + match self.str_start { + 0x1d => { + match param_idx { + // Finish last parameter if not already maxed + MAX_OSC_PARAMS => (), + + // First param is special - 0 to current byte index + 0 => { + self.osc_params[param_idx] = (0, idx); + self.osc_num_params += 1; + }, + + // All other params depend on previous indexing + _ => { + let prev = self.osc_params[param_idx - 1]; + let begin = prev.1; + self.osc_params[param_idx] = (begin, idx); + self.osc_num_params += 1; + }, + } + self.osc_dispatch(performer, byte); }, - - // All other params depend on previous indexing - _ => { - let prev = self.osc_params[param_idx - 1]; - let begin = prev.1; - self.osc_params[param_idx] = (begin, idx); - self.osc_num_params += 1; + 0x18 => { + self.sos_dispatch(performer); + }, + 0x1e => { + self.pm_dispatch(performer); + }, + 0x1F => { + self.apc_dispatch(performer); }, + // This condition should never be hit under the + // current way in which the code is ran. + _ => unreachable!(), } - self.osc_dispatch(performer, byte); }, Action::Unhook => performer.unhook(), Action::CsiDispatch => { @@ -428,6 +463,18 @@ pub trait Perform { /// The `ignore` flag indicates that more than two intermediates arrived and /// subsequent characters were ignored. fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {} + + /// Called at the end of an APC (application program command), where + /// `bytes` is the content of the APC + fn apc_dispatch(&mut self, _bytes: &[u8]) {} + + /// Called at the end of an SOS (start of string), where + /// `bytes` is the content of the SOS + fn sos_dispatch(&mut self, _bytes: &[u8]) {} + + /// Called at the end of a PM (privacy message), where + /// `bytes` is the content of the PM + fn pm_dispatch(&mut self, _bytes: &[u8]) {} } #[cfg(all(test, feature = "no_std"))] @@ -460,6 +507,9 @@ mod tests { DcsHook(Vec>, Vec, bool, char), DcsPut(u8), DcsUnhook, + Apc(Vec), + Pm(Vec), + Sos(Vec), } impl Perform for Dispatcher { @@ -492,6 +542,18 @@ mod tests { fn unhook(&mut self) { self.dispatched.push(Sequence::DcsUnhook); } + + fn apc_dispatch(&mut self, bytes: &[u8]) { + self.dispatched.push(Sequence::Apc(bytes.to_vec())) + } + + fn sos_dispatch(&mut self, bytes: &[u8]) { + self.dispatched.push(Sequence::Sos(bytes.to_vec())) + } + + fn pm_dispatch(&mut self, bytes: &[u8]) { + self.dispatched.push(Sequence::Pm(bytes.to_vec())) + } } #[test] @@ -880,6 +942,59 @@ mod tests { assert_eq!(dispatcher.dispatched[6], Sequence::DcsUnhook); } + #[test] + fn parse_apc() { + const INPUT: &[u8] = b"\x1b_abc\x1b\\"; + + // Test with ESC \ terminator + + let mut dispatcher = Dispatcher::default(); + let mut parser = Parser::new(); + + for byte in INPUT { + parser.advance(&mut dispatcher, *byte); + } + assert_eq!( + dispatcher.dispatched, + vec![Sequence::Apc(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] + ) + } + #[test] + fn parse_pm() { + const INPUT: &[u8] = b"\x1b^abc\x1b\\"; + + // Test with ESC \ terminator + + let mut dispatcher = Dispatcher::default(); + let mut parser = Parser::new(); + + for byte in INPUT { + parser.advance(&mut dispatcher, *byte); + } + assert_eq!( + dispatcher.dispatched, + vec![Sequence::Pm(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] + ) + } + + #[test] + fn parse_sos() { + const INPUT: &[u8] = b"\x1bXabc\x1b\\"; + + // Test with ESC \ terminator + + let mut dispatcher = Dispatcher::default(); + let mut parser = Parser::new(); + + for byte in INPUT { + parser.advance(&mut dispatcher, *byte); + } + assert_eq!( + dispatcher.dispatched, + vec![Sequence::Sos(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] + ) + } + #[test] fn intermediate_reset_on_dcs_exit() { static INPUT: &[u8] = b"\x1bP=1sZZZ\x1b+\x5c"; diff --git a/src/table.rs b/src/table.rs index f2c0105..42a713a 100644 --- a/src/table.rs +++ b/src/table.rs @@ -44,9 +44,9 @@ generate_state_changes!(state_changes, { 0x5b => (CsiEntry, None), 0x5d => (OscString, None), 0x50 => (DcsEntry, None), - 0x58 => (SosPmApcString, None), - 0x5e => (SosPmApcString, None), - 0x5f => (SosPmApcString, None), + 0x58 => (OscString, None), + 0x5e => (OscString, None), + 0x5f => (OscString, None), }, EscapeIntermediate { @@ -152,12 +152,8 @@ generate_state_changes!(state_changes, { 0x9c => (Ground, None), }, - SosPmApcString { - 0x00..=0x17 => (Anywhere, Ignore), - 0x19 => (Anywhere, Ignore), - 0x1c..=0x1f => (Anywhere, Ignore), - 0x20..=0x7f => (Anywhere, Ignore), - 0x9c => (Ground, None), + Null { + 0x00..=0xFF => (Anywhere, Ignore), }, OscString { @@ -166,6 +162,6 @@ generate_state_changes!(state_changes, { 0x08..=0x17 => (Anywhere, Ignore), 0x19 => (Anywhere, Ignore), 0x1c..=0x1f => (Anywhere, Ignore), - 0x20..=0xff => (Anywhere, OscPut), + 0x20..=0xff => (Anywhere, StringPut), } }); From cc99a0f7af2d73a48b6eb834041080a1f324911b Mon Sep 17 00:00:00 2001 From: John Toohey Date: Sun, 25 Feb 2024 12:23:56 +0000 Subject: [PATCH 6/8] Removed eprintln! statements for debugging --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fc121e0..a5c9102 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -268,7 +268,6 @@ impl Parser { self.osc_num_params = 0; }, Action::StringPut => { - eprintln!("{:x} {:x}: {:x}", self.str_start, self.str_start % 64, byte); #[cfg(feature = "no_std")] { if self.osc_raw.is_full() { @@ -304,7 +303,6 @@ impl Parser { } }, Action::StringEnd => { - eprintln!("str end"); let param_idx = self.osc_num_params; let idx = self.osc_raw.len(); From 642e207693a65da8311e3e8d5e407f8f6982edaf Mon Sep 17 00:00:00 2001 From: John Toohey Date: Sat, 9 Mar 2024 10:03:11 +0000 Subject: [PATCH 7/8] cargo fmt --- src/lib.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a5c9102..dd12eae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -952,10 +952,10 @@ mod tests { for byte in INPUT { parser.advance(&mut dispatcher, *byte); } - assert_eq!( - dispatcher.dispatched, - vec![Sequence::Apc(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] - ) + assert_eq!(dispatcher.dispatched, vec![ + Sequence::Apc(b"abc".to_vec()), + Sequence::Esc(vec![], false, 92) + ]) } #[test] fn parse_pm() { @@ -969,10 +969,10 @@ mod tests { for byte in INPUT { parser.advance(&mut dispatcher, *byte); } - assert_eq!( - dispatcher.dispatched, - vec![Sequence::Pm(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] - ) + assert_eq!(dispatcher.dispatched, vec![ + Sequence::Pm(b"abc".to_vec()), + Sequence::Esc(vec![], false, 92) + ]) } #[test] @@ -987,10 +987,10 @@ mod tests { for byte in INPUT { parser.advance(&mut dispatcher, *byte); } - assert_eq!( - dispatcher.dispatched, - vec![Sequence::Sos(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] - ) + assert_eq!(dispatcher.dispatched, vec![ + Sequence::Sos(b"abc".to_vec()), + Sequence::Esc(vec![], false, 92) + ]) } #[test] From 8e10a22e5688fc4a7309b1bb7ca9b3e7b6cc27d4 Mon Sep 17 00:00:00 2001 From: John Toohey Date: Sat, 9 Mar 2024 10:05:01 +0000 Subject: [PATCH 8/8] Added some fullstops to comments --- src/lib.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dd12eae..e40b66e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -463,15 +463,15 @@ pub trait Perform { fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {} /// Called at the end of an APC (application program command), where - /// `bytes` is the content of the APC + /// `bytes` is the content of the APC. fn apc_dispatch(&mut self, _bytes: &[u8]) {} /// Called at the end of an SOS (start of string), where - /// `bytes` is the content of the SOS + /// `bytes` is the content of the SOS. fn sos_dispatch(&mut self, _bytes: &[u8]) {} /// Called at the end of a PM (privacy message), where - /// `bytes` is the content of the PM + /// `bytes` is the content of the PM. fn pm_dispatch(&mut self, _bytes: &[u8]) {} } @@ -944,7 +944,7 @@ mod tests { fn parse_apc() { const INPUT: &[u8] = b"\x1b_abc\x1b\\"; - // Test with ESC \ terminator + // Test with ESC \ terminator. let mut dispatcher = Dispatcher::default(); let mut parser = Parser::new(); @@ -952,16 +952,16 @@ mod tests { for byte in INPUT { parser.advance(&mut dispatcher, *byte); } - assert_eq!(dispatcher.dispatched, vec![ - Sequence::Apc(b"abc".to_vec()), - Sequence::Esc(vec![], false, 92) - ]) + assert_eq!( + dispatcher.dispatched, + vec![Sequence::Apc(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] + ) } #[test] fn parse_pm() { const INPUT: &[u8] = b"\x1b^abc\x1b\\"; - // Test with ESC \ terminator + // Test with ESC \ terminator. let mut dispatcher = Dispatcher::default(); let mut parser = Parser::new(); @@ -969,17 +969,17 @@ mod tests { for byte in INPUT { parser.advance(&mut dispatcher, *byte); } - assert_eq!(dispatcher.dispatched, vec![ - Sequence::Pm(b"abc".to_vec()), - Sequence::Esc(vec![], false, 92) - ]) + assert_eq!( + dispatcher.dispatched, + vec![Sequence::Pm(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] + ) } #[test] fn parse_sos() { const INPUT: &[u8] = b"\x1bXabc\x1b\\"; - // Test with ESC \ terminator + // Test with ESC \ terminator. let mut dispatcher = Dispatcher::default(); let mut parser = Parser::new(); @@ -987,10 +987,10 @@ mod tests { for byte in INPUT { parser.advance(&mut dispatcher, *byte); } - assert_eq!(dispatcher.dispatched, vec![ - Sequence::Sos(b"abc".to_vec()), - Sequence::Esc(vec![], false, 92) - ]) + assert_eq!( + dispatcher.dispatched, + vec![Sequence::Sos(b"abc".to_vec()), Sequence::Esc(vec![], false, 92)] + ) } #[test]