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 1 commit
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
boazy marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
76 changes: 76 additions & 0 deletions 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 @@ -187,6 +188,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 +206,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 +371,24 @@ 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
boazy marked this conversation as resolved.
Show resolved Hide resolved
Action::OpaqueStart => {
let kind = match byte {
0x58 => OpaqueSequenceKind::Sos,
0x5e => OpaqueSequenceKind::Pm,
0x5f => 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"),
};
performer.opaque_start(kind)
},
Action::OpaquePut => performer.opaque_put(byte),
Action::OpaqueEnd => performer.opaque_end(),
}
}
}
Expand Down Expand Up @@ -428,6 +453,20 @@ 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 opaque sequence (SOS, PM or APC) has been detected.
///
/// The `kind` parameter indicates the type of sequence that was started.
///
/// Until the opaque sequence ends (at which point `opaque_end` will be called), invalid
/// characters will be ignored while valid characters will be passed on to `opaque_put`.
fn opaque_start(&mut self, _kind: OpaqueSequenceKind) {}

/// We've reached the end of the ongoing opaque sequence (SOS, PM or APC).
fn opaque_end(&mut self) {}

/// A byte has been received as part of an ongoing opaque sequence.
fn opaque_put(&mut self, _byte: u8) {}
boazy marked this conversation as resolved.
Show resolved Hide resolved
}

#[cfg(all(test, feature = "no_std"))]
Expand Down Expand Up @@ -460,6 +499,9 @@ mod tests {
DcsHook(Vec<Vec<u16>>, Vec<u8>, bool, char),
DcsPut(u8),
DcsUnhook,
OpaqueStart(OpaqueSequenceKind),
OpaquePut(u8),
OpaqueEnd,
}

impl Perform for Dispatcher {
Expand Down Expand Up @@ -492,6 +534,18 @@ mod tests {
fn unhook(&mut self) {
self.dispatched.push(Sequence::DcsUnhook);
}

fn opaque_start(&mut self, _kind: OpaqueSequenceKind) {
self.dispatched.push(Sequence::OpaqueStart(_kind));
}

fn opaque_put(&mut self, byte: u8) {
self.dispatched.push(Sequence::OpaquePut(byte));
}

fn opaque_end(&mut self) {
self.dispatched.push(Sequence::OpaqueEnd);
}
}

#[test]
Expand Down Expand Up @@ -628,6 +682,28 @@ mod tests {
}
}

#[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::OpaqueStart(OpaqueSequenceKind::Apc),
Sequence::OpaquePut(b'a'),
Sequence::OpaquePut(b'b'),
Sequence::OpaquePut(b'c'),
Sequence::OpaqueEnd,
])
}

#[test]
fn exceed_max_buffer_size() {
static NUM_BYTES: usize = MAX_OSC_RAW + 100;
Expand Down
10 changes: 5 additions & 5 deletions src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
},

Expand Down