Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for custom parsing of APC, SOS and PM sequences. #115

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 38 additions & 36 deletions src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub enum State {
#[default]
Ground = 12,
OscString = 13,
SosPmApcString = 14,
OpaqueString = 14,
Utf8 = 15,
}

Expand All @@ -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
Expand All @@ -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::<u8, State>(delta & 0x0f),
// Action is stored in top 4 bits
mem::transmute(delta >> 4),
mem::transmute::<u8, Action>(delta >> 4),
)
}
}
Expand All @@ -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"),
}

Expand All @@ -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);
}
}
203 changes: 202 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -89,6 +90,7 @@ pub struct Parser<const OSC_RAW_BUF_SIZE: usize = MAX_OSC_RAW> {
osc_num_params: usize,
ignoring: bool,
utf8_parser: utf8::Parser,
opaque_sequence_kind: Option<OpaqueSequenceKind>,
}

impl Parser {
Expand Down Expand Up @@ -187,6 +189,9 @@ impl<const OSC_RAW_BUF_SIZE: usize> Parser<OSC_RAW_BUF_SIZE> {
State::OscString => {
self.perform_action(performer, Action::OscEnd, byte);
},
State::OpaqueString => {
self.perform_action(performer, Action::OpaqueEnd, byte);
},
_ => (),
}

Expand All @@ -202,6 +207,9 @@ impl<const OSC_RAW_BUF_SIZE: usize> Parser<OSC_RAW_BUF_SIZE> {
State::OscString => {
self.perform_action(performer, Action::OscStart, byte);
},
State::OpaqueString => {
self.perform_action(performer, Action::OpaqueStart, byte);
},
_ => (),
}

Expand Down Expand Up @@ -364,6 +372,52 @@ impl<const OSC_RAW_BUF_SIZE: usize> Parser<OSC_RAW_BUF_SIZE> {
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;
},
}
}
}
Expand Down Expand Up @@ -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"))]
Expand Down Expand Up @@ -460,6 +550,15 @@ mod tests {
DcsHook(Vec<Vec<u16>>, Vec<u8>, bool, char),
DcsPut(u8),
DcsUnhook,
SosStart,
SosPut(u8),
SosEnd,
PmStart,
PmPut(u8),
PmEnd,
ApcStart,
ApcPut(u8),
ApcEnd,
}

impl Perform for Dispatcher {
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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"),
}
}
Expand Down
Loading