diff --git a/src/definitions.rs b/src/definitions.rs index 218c1eb..b5ea1d5 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -19,7 +19,7 @@ pub enum State { #[default] Ground = 12, OscString = 13, - SosPmApcString = 14, + OpaqueString = 14, Utf8 = 15, } @@ -28,21 +28,34 @@ pub enum State { #[derive(Debug, Clone, Copy)] pub enum Action { None = 0, - Clear = 1, - Collect = 2, - CsiDispatch = 3, - EscDispatch = 4, - Execute = 5, - Hook = 6, - Ignore = 7, - OscEnd = 8, - OscPut = 9, - OscStart = 10, - Param = 11, - Print = 12, - Put = 13, - Unhook = 14, - BeginUtf8 = 15, + Collect = 1, + CsiDispatch = 2, + EscDispatch = 3, + Execute = 4, + Ignore = 5, + OscPut = 6, + Param = 7, + Print = 8, + Put = 9, + BeginUtf8 = 10, + OpaquePut = 11, + + // Actions that do not need to be packed as 4 bits in the state table + // can have values higher than 16. + Clear = 16, + Hook = 17, + Unhook = 18, + OscStart = 19, + OscEnd = 20, + OpaqueStart = 21, + OpaqueEnd = 22, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum OpaqueSequenceKind { + Sos, + Pm, + Apc, } /// Unpack a u8 into a State and Action @@ -57,9 +70,9 @@ pub fn unpack(delta: u8) -> (State, Action) { unsafe { ( // State is stored in bottom 4 bits - mem::transmute(delta & 0x0f), + mem::transmute::(delta & 0x0f), // Action is stored in top 4 bits - mem::transmute(delta >> 4), + mem::transmute::(delta >> 4), ) } } @@ -75,8 +88,8 @@ mod tests { #[test] fn unpack_state_action() { - match unpack(0xee) { - (State::SosPmApcString, Action::Unhook) => (), + match unpack(0xaa) { + (State::Escape, Action::BeginUtf8) => (), _ => panic!("unpack failed"), } @@ -85,27 +98,16 @@ mod tests { _ => panic!("unpack failed"), } - match unpack(0xff) { - (State::Utf8, Action::BeginUtf8) => (), + match unpack(0xbf) { + (State::Utf8, Action::OpaquePut) => (), _ => 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"), - } + assert_eq!(pack(State::Escape, Action::BeginUtf8), 0xaa); + assert_eq!(pack(State::Utf8, Action::None), 0x0f); + assert_eq!(pack(State::Utf8, Action::OpaquePut), 0xbf); } } diff --git a/src/lib.rs b/src/lib.rs index 31e2a31..17d7b43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,7 @@ mod table; pub mod ansi; pub use params::{Params, ParamsIter}; +use crate::definitions::OpaqueSequenceKind; use definitions::{unpack, Action, State}; const MAX_INTERMEDIATES: usize = 2; @@ -89,6 +90,7 @@ pub struct Parser { osc_num_params: usize, ignoring: bool, utf8_parser: utf8::Parser, + opaque_sequence_kind: Option, } impl Parser { @@ -187,6 +189,9 @@ impl Parser { State::OscString => { self.perform_action(performer, Action::OscEnd, byte); }, + State::OpaqueString => { + self.perform_action(performer, Action::OpaqueEnd, byte); + }, _ => (), } @@ -202,6 +207,9 @@ impl Parser { State::OscString => { self.perform_action(performer, Action::OscStart, byte); }, + State::OpaqueString => { + self.perform_action(performer, Action::OpaqueStart, byte); + }, _ => (), } @@ -364,6 +372,52 @@ impl Parser { Action::BeginUtf8 => self.process_utf8(performer, byte), Action::Ignore => (), Action::None => (), + + // APC Actions are checked last, since they are relatively rare. + Action::OpaqueStart => { + let kind = match byte { + 0x58 => { + performer.sos_start(); + OpaqueSequenceKind::Sos + }, + 0x5e => { + performer.pm_start(); + OpaqueSequenceKind::Pm + }, + 0x5f => { + performer.apc_start(); + OpaqueSequenceKind::Apc + }, + + // Changes to OpaqueString state which trigger this action are only possible + // when one of the escape sequences above is detected (see Escape state changes + // in table.rs). Since there is no other way to reach this action with any other + // byte value, this branch is unreachable. + _ => unreachable!("invalid opaque sequence kind"), + }; + self.opaque_sequence_kind = Some(kind); + }, + Action::OpaquePut => { + match self.opaque_sequence_kind { + Some(OpaqueSequenceKind::Sos) => performer.sos_put(byte), + Some(OpaqueSequenceKind::Pm) => performer.pm_put(byte), + Some(OpaqueSequenceKind::Apc) => performer.apc_put(byte), + // This action is only triggered inside the OpaqueString state, which requires + // that the opaque_sequence_kind is set to a Some(x) value. + None => unreachable!("opaque sequence kind not set"), + } + }, + Action::OpaqueEnd => { + match self.opaque_sequence_kind { + Some(OpaqueSequenceKind::Sos) => performer.sos_end(), + Some(OpaqueSequenceKind::Pm) => performer.pm_end(), + Some(OpaqueSequenceKind::Apc) => performer.apc_end(), + // This action is only triggered inside the OpaqueString state, which requires + // that the opaque_sequence_kind is set to a Some(x) value. + None => unreachable!("opaque sequence kind not set"), + } + self.opaque_sequence_kind = None; + }, } } } @@ -428,6 +482,42 @@ 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) {} + + /// The start of an SOS sequence has been detected. + /// + /// Until the SOS sequence ends (at which point `sos_end` will be called), invalid + /// characters will be ignored while valid characters will be passed on to `sos_put`. + fn sos_start(&mut self) {} + + /// A byte has been received as part of an ongoing SOS sequence. + fn sos_put(&mut self, _byte: u8) {} + + /// We've reached the end of the ongoing SOS sequence. + fn sos_end(&mut self) {} + + /// The start of a PM sequence has been detected. + /// + /// Until the PM sequence ends (at which point `pm_end` will be called), invalid + /// characters will be ignored while valid characters will be passed on to `pm_put`. + fn pm_start(&mut self) {} + + /// A byte has been received as part of an ongoing PM sequence. + fn pm_put(&mut self, _byte: u8) {} + + /// We've reached the end of the ongoing PM sequence. + fn pm_end(&mut self) {} + + /// The start of an APC sequence has been detected. + /// + /// Until the APC sequence ends (at which point `apc_end` will be called), invalid + /// characters will be ignored while valid characters will be passed on to `apc_put`. + fn apc_start(&mut self) {} + + /// A byte has been received as part of an ongoing APC sequence. + fn apc_put(&mut self, _byte: u8) {} + + /// We've reached the end of the ongoing APC sequence. + fn apc_end(&mut self) {} } #[cfg(all(test, feature = "no_std"))] @@ -460,6 +550,15 @@ mod tests { DcsHook(Vec>, Vec, bool, char), DcsPut(u8), DcsUnhook, + SosStart, + SosPut(u8), + SosEnd, + PmStart, + PmPut(u8), + PmEnd, + ApcStart, + ApcPut(u8), + ApcEnd, } impl Perform for Dispatcher { @@ -492,6 +591,42 @@ mod tests { fn unhook(&mut self) { self.dispatched.push(Sequence::DcsUnhook); } + + fn sos_start(&mut self) { + self.dispatched.push(Sequence::SosStart); + } + + fn sos_put(&mut self, byte: u8) { + self.dispatched.push(Sequence::SosPut(byte)); + } + + fn sos_end(&mut self) { + self.dispatched.push(Sequence::SosEnd); + } + + fn pm_start(&mut self) { + self.dispatched.push(Sequence::PmStart); + } + + fn pm_put(&mut self, byte: u8) { + self.dispatched.push(Sequence::PmPut(byte)); + } + + fn pm_end(&mut self) { + self.dispatched.push(Sequence::PmEnd); + } + + fn apc_start(&mut self) { + self.dispatched.push(Sequence::ApcStart); + } + + fn apc_put(&mut self, byte: u8) { + self.dispatched.push(Sequence::ApcPut(byte)); + } + + fn apc_end(&mut self) { + self.dispatched.push(Sequence::ApcEnd); + } } #[test] @@ -628,6 +763,72 @@ mod tests { } } + #[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.len(), 6); + assert_eq!(dispatcher.dispatched[0..5], vec![ + Sequence::SosStart, + Sequence::SosPut(b'a'), + Sequence::SosPut(b'b'), + Sequence::SosPut(b'c'), + Sequence::SosEnd, + ]) + } + + #[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.len(), 6); + assert_eq!(dispatcher.dispatched[0..5], vec![ + Sequence::PmStart, + Sequence::PmPut(b'a'), + Sequence::PmPut(b'b'), + Sequence::PmPut(b'c'), + Sequence::PmEnd, + ]) + } + + #[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.len(), 6); + assert_eq!(dispatcher.dispatched[0..5], vec![ + Sequence::ApcStart, + Sequence::ApcPut(b'a'), + Sequence::ApcPut(b'b'), + Sequence::ApcPut(b'c'), + Sequence::ApcEnd, + ]) + } + #[test] fn exceed_max_buffer_size() { static NUM_BYTES: usize = MAX_OSC_RAW + 100; @@ -764,7 +965,7 @@ mod tests { assert_eq!(dispatcher.dispatched.len(), 1); match &dispatcher.dispatched[0] { - Sequence::Csi(params, ..) => assert_eq!(params, &[[std::u16::MAX as u16]]), + Sequence::Csi(params, ..) => assert_eq!(params, &[[std::u16::MAX]]), _ => panic!("expected csi sequence"), } } diff --git a/src/table.rs b/src/table.rs index f2c0105..31628b1 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 => (OpaqueString, None), + 0x5e => (OpaqueString, None), + 0x5f => (OpaqueString, None), }, EscapeIntermediate { @@ -152,11 +152,11 @@ generate_state_changes!(state_changes, { 0x9c => (Ground, None), }, - SosPmApcString { + OpaqueString { 0x00..=0x17 => (Anywhere, Ignore), 0x19 => (Anywhere, Ignore), 0x1c..=0x1f => (Anywhere, Ignore), - 0x20..=0x7f => (Anywhere, Ignore), + 0x20..=0x7f => (Anywhere, OpaquePut), 0x9c => (Ground, None), },