#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)]
use std::str;
mod params;
pub use params::{Params, ParamsIter};
const MAX_INTERMEDIATES: usize = 4;
const MAX_OSC_PARAMS: usize = 16;
const OSC_FIXED_LEN: usize = 2048;
#[derive(Default)]
pub struct Parser {
state: State,
intermediates: [u8; MAX_INTERMEDIATES],
intermediate_idx: usize,
params: Params,
param: u16,
osc_raw: OscBuffer,
osc_params: [(usize, usize); MAX_OSC_PARAMS],
osc_num_params: usize,
ignoring: bool,
partial_utf8: [u8; 4],
partial_utf8_len: usize,
decode_buf: Vec<u32>,
}
struct OscBuffer {
fixed: [u8; OSC_FIXED_LEN],
fixed_len: usize,
overflow: Vec<u8>,
}
impl Default for OscBuffer {
fn default() -> Self {
Self {
fixed: [0; OSC_FIXED_LEN],
fixed_len: 0,
overflow: Vec::new(),
}
}
}
impl OscBuffer {
#[inline]
fn len(&self) -> usize {
if self.overflow.is_empty() {
self.fixed_len
} else {
self.overflow.len()
}
}
#[inline]
fn push(&mut self, byte: u8) {
if self.overflow.is_empty() {
if self.fixed_len < OSC_FIXED_LEN {
self.fixed[self.fixed_len] = byte;
self.fixed_len += 1;
return;
}
self.overflow
.extend_from_slice(&self.fixed[..self.fixed_len]);
}
self.overflow.push(byte);
}
#[inline]
fn extend_from_slice(&mut self, bytes: &[u8]) {
if self.overflow.is_empty() {
let fits = OSC_FIXED_LEN - self.fixed_len;
if bytes.len() <= fits {
self.fixed[self.fixed_len..self.fixed_len + bytes.len()]
.copy_from_slice(bytes);
self.fixed_len += bytes.len();
return;
}
self.overflow
.extend_from_slice(&self.fixed[..self.fixed_len]);
}
self.overflow.extend_from_slice(bytes);
}
#[inline]
fn slice(&self, start: usize, end: usize) -> &[u8] {
if self.overflow.is_empty() {
&self.fixed[start..end]
} else {
&self.overflow[start..end]
}
}
#[inline]
fn clear(&mut self) {
self.fixed_len = 0;
self.overflow.clear();
}
}
impl Parser {
pub fn new() -> Self {
Self::default()
}
#[inline]
fn params(&self) -> &Params {
&self.params
}
#[inline]
fn intermediates(&self) -> &[u8] {
&self.intermediates[..self.intermediate_idx]
}
#[inline]
pub fn advance<P: Perform>(&mut self, performer: &mut P, bytes: &[u8]) {
let mut i = 0;
if self.partial_utf8_len != 0 {
i += self.advance_partial_utf8(performer, bytes);
}
while i != bytes.len() {
match self.state {
State::Ground => i += self.advance_ground(performer, &bytes[i..]),
State::CsiParam => {
i += self.advance_csi_param_run(performer, &bytes[i..])
}
State::OscString => {
i += self.advance_osc_string_run(performer, &bytes[i..])
}
State::ApcString => {
i += self.advance_apc_string_run(performer, &bytes[i..])
}
State::SosString => {
i += self.advance_sos_string_run(performer, &bytes[i..])
}
State::PmString => {
i += self.advance_pm_string_run(performer, &bytes[i..])
}
State::DcsPassthrough => {
i += self.advance_dcs_passthrough_run(performer, &bytes[i..])
}
_ => {
let byte = bytes[i];
self.change_state(performer, byte);
i += 1;
}
}
}
}
fn advance_csi_param_run<P: Perform>(
&mut self,
performer: &mut P,
bytes: &[u8],
) -> usize {
let mut i = 0;
while i < bytes.len() {
let byte = bytes[i];
match byte {
0x30..=0x39 => {
if self.params.is_full() {
self.ignoring = true;
i += 1;
} else {
let mut param = self.param;
while i < bytes.len() && bytes[i].is_ascii_digit() {
param = param
.saturating_mul(10)
.saturating_add((bytes[i] - b'0') as u16);
i += 1;
}
self.param = param;
}
}
0x3A => {
self.action_subparam();
i += 1;
}
0x3B => {
self.action_param();
i += 1;
}
_ => {
self.advance_csi_param(performer, byte);
i += 1;
if self.state != State::CsiParam {
break;
}
}
}
}
i
}
#[inline(always)]
fn change_state<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match self.state {
State::CsiEntry => self.advance_csi_entry(performer, byte),
State::CsiIgnore => self.advance_csi_ignore(performer, byte),
State::CsiIntermediate => self.advance_csi_intermediate(performer, byte),
State::CsiParam => self.advance_csi_param(performer, byte),
State::DcsEntry => self.advance_dcs_entry(performer, byte),
State::DcsIgnore => self.anywhere(performer, byte),
State::DcsIntermediate => self.advance_dcs_intermediate(performer, byte),
State::DcsParam => self.advance_dcs_param(performer, byte),
State::DcsPassthrough => self.advance_dcs_passthrough(performer, byte),
State::Escape => self.advance_esc(performer, byte),
State::EscapeIntermediate => self.advance_esc_intermediate(performer, byte),
State::OscString => self.advance_osc_string(performer, byte),
State::SosString => self.advance_sos_string(performer, byte),
State::ApcString => self.advance_apc_string(performer, byte),
State::PmString => self.advance_pm_string(performer, byte),
State::Ground => unreachable!(),
}
}
#[inline(always)]
fn advance_csi_entry<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte),
0x20..=0x2F => {
self.action_collect(byte);
self.state = State::CsiIntermediate
}
0x30..=0x39 => {
self.action_paramnext(byte);
self.state = State::CsiParam
}
0x3A => {
self.action_subparam();
self.state = State::CsiParam
}
0x3B => {
self.action_param();
self.state = State::CsiParam
}
0x3C..=0x3F => {
self.action_collect(byte);
self.state = State::CsiParam
}
0x40..=0x7E => self.action_csi_dispatch(performer, byte),
_ => self.anywhere(performer, byte),
}
}
#[inline(always)]
fn advance_csi_ignore<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte),
0x20..=0x3F => (),
0x40..=0x7E => self.state = State::Ground,
0x7F => (),
_ => self.anywhere(performer, byte),
}
}
#[inline(always)]
fn advance_csi_intermediate<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte),
0x20..=0x2F => self.action_collect(byte),
0x30..=0x3F => self.state = State::CsiIgnore,
0x40..=0x7E => self.action_csi_dispatch(performer, byte),
_ => self.anywhere(performer, byte),
}
}
#[inline(always)]
fn advance_csi_param<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte),
0x20..=0x2F => {
self.action_collect(byte);
self.state = State::CsiIntermediate
}
0x30..=0x39 => self.action_paramnext(byte),
0x3A => self.action_subparam(),
0x3B => self.action_param(),
0x3C..=0x3F => self.state = State::CsiIgnore,
0x40..=0x7E => self.action_csi_dispatch(performer, byte),
0x7F => (),
_ => self.anywhere(performer, byte),
}
}
#[inline(always)]
fn advance_dcs_entry<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x17 | 0x19 | 0x1C..=0x1F => (),
0x20..=0x2F => {
self.action_collect(byte);
self.state = State::DcsIntermediate
}
0x30..=0x39 => {
self.action_paramnext(byte);
self.state = State::DcsParam
}
0x3A => {
self.action_subparam();
self.state = State::DcsParam
}
0x3B => {
self.action_param();
self.state = State::DcsParam
}
0x3C..=0x3F => {
self.action_collect(byte);
self.state = State::DcsParam
}
0x40..=0x7E => self.action_hook(performer, byte),
0x7F => (),
_ => self.anywhere(performer, byte),
}
}
#[inline(always)]
fn advance_dcs_intermediate<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x17 | 0x19 | 0x1C..=0x1F => (),
0x20..=0x2F => self.action_collect(byte),
0x30..=0x3F => self.state = State::DcsIgnore,
0x40..=0x7E => self.action_hook(performer, byte),
0x7F => (),
_ => self.anywhere(performer, byte),
}
}
#[inline(always)]
fn advance_dcs_param<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x17 | 0x19 | 0x1C..=0x1F => (),
0x20..=0x2F => {
self.action_collect(byte);
self.state = State::DcsIntermediate
}
0x30..=0x39 => self.action_paramnext(byte),
0x3A => self.action_subparam(),
0x3B => self.action_param(),
0x3C..=0x3F => self.state = State::DcsIgnore,
0x40..=0x7E => self.action_hook(performer, byte),
0x7F => (),
_ => self.anywhere(performer, byte),
}
}
#[inline(always)]
fn advance_dcs_passthrough<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x17 | 0x19 | 0x1C..=0x7E => performer.put(byte),
0x18 | 0x1A => {
performer.unhook();
performer.execute(byte);
self.state = State::Ground
}
0x1B => {
performer.unhook();
self.reset_params();
self.state = State::Escape
}
0x7F => (),
0x9C => {
performer.unhook();
self.state = State::Ground
}
_ => (),
}
}
#[inline(always)]
fn advance_esc<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte),
0x20..=0x2F => {
self.action_collect(byte);
self.state = State::EscapeIntermediate
}
0x30..=0x4F => {
performer.esc_dispatch(self.intermediates(), self.ignoring, byte);
self.state = State::Ground
}
0x50 => {
self.reset_params();
self.state = State::DcsEntry
}
0x51..=0x57 => {
performer.esc_dispatch(self.intermediates(), self.ignoring, byte);
self.state = State::Ground
}
0x58 => {
performer.sos_start();
self.state = State::SosString
}
0x59..=0x5A => {
performer.esc_dispatch(self.intermediates(), self.ignoring, byte);
self.state = State::Ground
}
0x5B => {
self.reset_params();
self.state = State::CsiEntry
}
0x5C => {
performer.esc_dispatch(self.intermediates(), self.ignoring, byte);
self.state = State::Ground
}
0x5D => {
self.state = State::OscString
}
0x5E => {
performer.pm_start();
self.state = State::PmString
}
0x5F => {
performer.apc_start();
self.state = State::ApcString
}
0x60..=0x7E => {
performer.esc_dispatch(self.intermediates(), self.ignoring, byte);
self.state = State::Ground
}
0x18 | 0x1A => {
performer.execute(byte);
self.state = State::Ground
}
0x1B => (),
_ => (),
}
}
#[inline(always)]
fn advance_esc_intermediate<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte),
0x20..=0x2F => self.action_collect(byte),
0x30..=0x7E => {
performer.esc_dispatch(self.intermediates(), self.ignoring, byte);
self.state = State::Ground
}
0x7F => (),
_ => self.anywhere(performer, byte),
}
}
#[inline(always)]
fn advance_osc_string_run<P: Perform>(
&mut self,
performer: &mut P,
bytes: &[u8],
) -> usize {
let n = find_osc_boundary(bytes);
if n != 0 {
self.osc_raw.extend_from_slice(&bytes[..n]);
}
if n == bytes.len() {
return n;
}
self.advance_osc_string(performer, bytes[n]);
n + 1
}
fn advance_apc_string_run<P: Perform>(
&mut self,
performer: &mut P,
bytes: &[u8],
) -> usize {
let n = find_string_c0(bytes);
if n != 0 {
performer.apc_put_slice(&bytes[..n]);
}
if n == bytes.len() {
return n;
}
self.advance_apc_string(performer, bytes[n]);
n + 1
}
fn advance_sos_string_run<P: Perform>(
&mut self,
performer: &mut P,
bytes: &[u8],
) -> usize {
let n = find_string_c0(bytes);
if n != 0 {
performer.sos_put_slice(&bytes[..n]);
}
if n == bytes.len() {
return n;
}
self.advance_sos_string(performer, bytes[n]);
n + 1
}
fn advance_pm_string_run<P: Perform>(
&mut self,
performer: &mut P,
bytes: &[u8],
) -> usize {
let n = find_string_c0(bytes);
if n != 0 {
performer.pm_put_slice(&bytes[..n]);
}
if n == bytes.len() {
return n;
}
self.advance_pm_string(performer, bytes[n]);
n + 1
}
fn advance_dcs_passthrough_run<P: Perform>(
&mut self,
performer: &mut P,
bytes: &[u8],
) -> usize {
let n = find_dcs_boundary(bytes);
if n != 0 {
performer.put_slice(&bytes[..n]);
}
if n == bytes.len() {
return n;
}
self.advance_dcs_passthrough(performer, bytes[n]);
n + 1
}
fn advance_osc_string<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x06 | 0x08..=0x17 | 0x19 | 0x1C..=0x1F => (),
0x07 => {
self.osc_end(performer, byte);
self.state = State::Ground
}
0x18 | 0x1A => {
self.osc_end(performer, byte);
performer.execute(byte);
self.state = State::Ground
}
0x1B => {
self.osc_end(performer, byte);
self.reset_params();
self.state = State::Escape
}
0x3B => self.action_osc_put_param(),
_ => self.action_osc_put(byte),
}
}
#[inline(always)]
fn advance_apc_string<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x00..=0x06 | 0x08..=0x17 | 0x19 | 0x1C..=0x1F => (), 0x07 => {
performer.apc_end();
self.state = State::Ground;
}
0x18 | 0x1A => {
performer.apc_put(byte);
performer.apc_end();
performer.execute(byte);
self.state = State::Ground;
}
0x1B => {
performer.apc_end();
self.state = State::Escape;
}
0x20..=0xFF => performer.apc_put(byte),
}
}
#[inline(always)]
fn advance_sos_string<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x07 => {
performer.sos_end();
self.state = State::Ground
}
0x18 | 0x1A => {
performer.sos_end();
performer.execute(byte);
self.state = State::Ground
}
0x1B => {
performer.sos_end();
self.state = State::Escape
}
0x20..=0xFF => performer.sos_put(byte),
_ => (),
}
}
#[inline(always)]
fn advance_pm_string<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x07 => {
performer.pm_end();
self.state = State::Ground
}
0x18 | 0x1A => {
performer.pm_end();
performer.execute(byte);
self.state = State::Ground
}
0x1B => {
performer.pm_end();
self.state = State::Escape
}
0x20..=0xFF => performer.pm_put(byte),
_ => (),
}
}
#[inline(always)]
fn anywhere<P: Perform>(&mut self, performer: &mut P, byte: u8) {
match byte {
0x18 | 0x1A => {
performer.execute(byte);
self.state = State::Ground
}
0x1B => {
self.reset_params();
self.state = State::Escape
}
_ => (),
}
}
#[inline]
fn action_csi_dispatch<P: Perform>(&mut self, performer: &mut P, byte: u8) {
if self.params.is_full() {
self.ignoring = true;
} else {
self.params.push(self.param);
}
performer.csi_dispatch(
self.params(),
self.intermediates(),
self.ignoring,
byte as char,
);
self.state = State::Ground
}
#[inline]
fn action_hook<P: Perform>(&mut self, performer: &mut P, byte: u8) {
if self.params.is_full() {
self.ignoring = true;
} else {
self.params.push(self.param);
}
performer.hook(
self.params(),
self.intermediates(),
self.ignoring,
byte as char,
);
self.state = State::DcsPassthrough;
}
#[inline]
fn action_collect(&mut self, byte: u8) {
if self.intermediate_idx == MAX_INTERMEDIATES {
self.ignoring = true;
} else {
self.intermediates[self.intermediate_idx] = byte;
self.intermediate_idx += 1;
}
}
#[inline]
fn action_subparam(&mut self) {
if self.params.is_full() {
self.ignoring = true;
} else {
self.params.extend(self.param);
self.param = 0;
}
}
#[inline]
fn action_param(&mut self) {
if self.params.is_full() {
self.ignoring = true;
} else {
self.params.push(self.param);
self.param = 0;
}
}
#[inline]
fn action_paramnext(&mut self, byte: u8) {
if self.params.is_full() {
self.ignoring = true;
} else {
self.param = self.param.saturating_mul(10);
self.param = self.param.saturating_add((byte - b'0') as u16);
}
}
#[inline]
fn action_osc_put_param(&mut self) {
let idx = self.osc_raw.len();
let param_idx = self.osc_num_params;
match param_idx {
0 => self.osc_params[param_idx] = (0, idx),
MAX_OSC_PARAMS => return,
_ => {
let prev = self.osc_params[param_idx - 1];
let begin = prev.1;
self.osc_params[param_idx] = (begin, idx);
}
}
self.osc_num_params += 1;
}
#[inline(always)]
fn action_osc_put(&mut self, byte: u8) {
self.osc_raw.push(byte);
}
fn osc_end<P: Perform>(&mut self, performer: &mut P, byte: u8) {
self.action_osc_put_param();
self.osc_dispatch(performer, byte);
self.osc_raw.clear();
self.osc_num_params = 0;
}
#[inline]
fn reset_params(&mut self) {
self.intermediate_idx = 0;
self.ignoring = false;
self.param = 0;
self.params.clear();
}
#[inline]
fn osc_dispatch<P: Perform>(&self, performer: &mut P, byte: u8) {
let mut slices: [&[u8]; MAX_OSC_PARAMS] = [&[]; MAX_OSC_PARAMS];
for (slice, &(start, end)) in slices
.iter_mut()
.zip(self.osc_params.iter())
.take(self.osc_num_params)
{
*slice = self.osc_raw.slice(start, end);
}
performer.osc_dispatch(&slices[..self.osc_num_params], byte == 0x07);
}
#[inline]
fn advance_ground<P: Perform>(&mut self, performer: &mut P, bytes: &[u8]) -> usize {
let num_bytes = bytes.len();
let plain_chars = memchr::memchr(0x1B, bytes).unwrap_or(num_bytes);
if plain_chars == 0 {
self.state = State::Escape;
self.reset_params();
return 1;
}
let has_trailing_esc = plain_chars < num_bytes;
let prefix = &bytes[..plain_chars];
let process_len = if has_trailing_esc {
plain_chars
} else {
trim_valid_partial_utf8(prefix)
};
self.ground_dispatch(performer, &prefix[..process_len]);
if process_len < plain_chars {
let partial = &prefix[process_len..];
let dst_start = self.partial_utf8_len;
let dst_end = dst_start + partial.len();
self.partial_utf8[dst_start..dst_end].copy_from_slice(partial);
self.partial_utf8_len = dst_end;
return num_bytes;
}
if has_trailing_esc {
self.state = State::Escape;
self.reset_params();
plain_chars + 1
} else {
num_bytes
}
}
#[inline]
fn advance_partial_utf8<P: Perform>(
&mut self,
performer: &mut P,
bytes: &[u8],
) -> usize {
let old_bytes = self.partial_utf8_len;
let to_copy = bytes.len().min(self.partial_utf8.len() - old_bytes);
self.partial_utf8[old_bytes..old_bytes + to_copy]
.copy_from_slice(&bytes[..to_copy]);
self.partial_utf8_len += to_copy;
match crate::simd_utf8::validate(&self.partial_utf8[..self.partial_utf8_len]) {
Ok(parsed) => {
let c = unsafe { parsed.chars().next().unwrap_unchecked() };
performer.print(c);
self.partial_utf8_len = 0;
c.len_utf8() - old_bytes
}
Err(err) => {
let valid_bytes = err.valid_up_to();
if valid_bytes > 0 {
let c = unsafe {
let parsed =
str::from_utf8_unchecked(&self.partial_utf8[..valid_bytes]);
parsed.chars().next().unwrap_unchecked()
};
performer.print(c);
self.partial_utf8_len = 0;
return valid_bytes - old_bytes;
}
match err.error_len() {
Some(invalid_len) => {
performer.print('�');
self.partial_utf8_len = 0;
invalid_len - old_bytes
}
None => to_copy,
}
}
}
}
#[inline]
fn ground_dispatch<P: Perform>(&mut self, performer: &mut P, bytes: &[u8]) {
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if (0x20..=0x7E).contains(&b) {
let end = i + find_non_printable(&bytes[i..]);
let chunk = unsafe { std::str::from_utf8_unchecked(&bytes[i..end]) };
performer.print_str(chunk);
i = end;
} else if b < 0x80 {
performer.execute(b);
i += 1;
} else if (0x80..=0x9F).contains(&b) {
performer.execute(b);
i += 1;
} else {
let mut end = (i + DECODE_CHUNK).min(bytes.len());
if end < bytes.len() {
let start = end;
while end > i && (bytes[end] & 0xC0) == 0x80 {
end -= 1;
}
if end == i {
end = start;
}
}
self.decode_codepoints(&bytes[i..end]);
Self::dispatch_codepoints(performer, &self.decode_buf);
i = end;
}
}
}
#[inline]
fn decode_codepoints_scalar(&mut self, src: &[u8]) {
self.decode_buf.clear();
self.decode_buf.reserve(src.len());
let mut consumed = 0;
while consumed < src.len() {
match std::str::from_utf8(&src[consumed..]) {
Ok(valid) => {
self.decode_buf.extend(valid.chars().map(|c| c as u32));
return;
}
Err(err) => {
let end = consumed + err.valid_up_to();
let valid =
unsafe { std::str::from_utf8_unchecked(&src[consumed..end]) };
self.decode_buf.extend(valid.chars().map(|c| c as u32));
let subpart = maximal_subpart(&src[end..]);
if subpart == 1 && (0x80..=0x9F).contains(&src[end]) {
self.decode_buf.push(src[end] as u32);
} else {
self.decode_buf.push(0xFFFD);
}
consumed = end + subpart;
}
}
}
}
#[inline]
fn decode_codepoints(&mut self, src: &[u8]) {
#[cfg(not(target_arch = "wasm32"))]
if src.len() >= SIMD_DECODE_MIN {
return self.decode_codepoints_simd(src);
}
self.decode_codepoints_scalar(src)
}
#[cfg(not(target_arch = "wasm32"))]
#[inline]
fn decode_codepoints_simd(&mut self, src: &[u8]) {
self.decode_buf.clear();
self.decode_buf.reserve(src.len());
let mut consumed = 0;
while consumed < src.len() {
let result = unsafe {
simdutf::convert_utf8_to_utf32_with_errors(
src.as_ptr().add(consumed),
src.len() - consumed,
self.decode_buf.as_mut_ptr().add(self.decode_buf.len()),
)
};
if result.error == simdutf::ErrorCode::Success {
unsafe {
self.decode_buf
.set_len(self.decode_buf.len() + result.count);
}
return;
}
let valid_prefix = &src[consumed..consumed + result.count];
let written = simdutf::count_utf8(valid_prefix);
unsafe {
self.decode_buf.set_len(self.decode_buf.len() + written);
}
let err_pos = consumed + result.count;
let subpart = maximal_subpart(&src[err_pos..]);
if subpart == 1 && (0x80..=0x9F).contains(&src[err_pos]) {
self.decode_buf.push(src[err_pos] as u32);
} else {
self.decode_buf.push(0xFFFD);
}
consumed = err_pos + subpart;
}
}
#[inline]
fn dispatch_codepoints<P: Perform>(performer: &mut P, codepoints: &[u32]) {
let mut start = 0;
for (i, &cp) in codepoints.iter().enumerate() {
if cp < 0x20 || cp == 0x7F || (0x80..=0x9F).contains(&cp) {
if start < i {
performer.print_codepoints(&codepoints[start..i]);
}
performer.execute(cp as u8);
start = i + 1;
}
}
if start < codepoints.len() {
performer.print_codepoints(&codepoints[start..]);
}
}
}
#[inline]
fn find_non_printable(bytes: &[u8]) -> usize {
const LO: u64 = 0x0101_0101_0101_0101;
const HI: u64 = 0x8080_8080_8080_8080;
let mut i = 0;
let len = bytes.len();
while i + 8 <= len {
let w = u64::from_le_bytes(bytes[i..i + 8].try_into().unwrap());
let lt20 = w.wrapping_sub(LO * 0x20) & !w & HI;
let ge80 = w & HI;
let x = w ^ (LO * 0x7F);
let eq7f = x.wrapping_sub(LO) & !x & HI;
let stop = lt20 | ge80 | eq7f;
if stop != 0 {
return i + (stop.trailing_zeros() as usize) / 8;
}
i += 8;
}
while i < len {
if !(0x20..=0x7E).contains(&bytes[i]) {
return i;
}
i += 1;
}
len
}
fn find_osc_boundary(bytes: &[u8]) -> usize {
const LO: u64 = 0x0101_0101_0101_0101;
const HI: u64 = 0x8080_8080_8080_8080;
let mut i = 0;
let len = bytes.len();
while i + 8 <= len {
let w = u64::from_le_bytes(bytes[i..i + 8].try_into().unwrap());
let lt20 = w.wrapping_sub(LO * 0x20) & !w & HI;
let x = w ^ (LO * 0x3B);
let eq3b = x.wrapping_sub(LO) & !x & HI;
let stop = lt20 | eq3b;
if stop != 0 {
return i + (stop.trailing_zeros() as usize) / 8;
}
i += 8;
}
while i < len {
if bytes[i] < 0x20 || bytes[i] == 0x3B {
return i;
}
i += 1;
}
len
}
fn find_string_c0(bytes: &[u8]) -> usize {
const LO: u64 = 0x0101_0101_0101_0101;
const HI: u64 = 0x8080_8080_8080_8080;
let mut i = 0;
let len = bytes.len();
while i + 8 <= len {
let w = u64::from_le_bytes(bytes[i..i + 8].try_into().unwrap());
let lt20 = w.wrapping_sub(LO * 0x20) & !w & HI;
if lt20 != 0 {
return i + (lt20.trailing_zeros() as usize) / 8;
}
i += 8;
}
while i < len {
if bytes[i] < 0x20 {
return i;
}
i += 1;
}
len
}
fn find_dcs_boundary(bytes: &[u8]) -> usize {
const LO: u64 = 0x0101_0101_0101_0101;
const HI: u64 = 0x8080_8080_8080_8080;
#[inline(always)]
fn eq(w: u64, b: u8) -> u64 {
const LO: u64 = 0x0101_0101_0101_0101;
const HI: u64 = 0x8080_8080_8080_8080;
let x = w ^ (LO * b as u64);
x.wrapping_sub(LO) & !x & HI
}
let mut i = 0;
let len = bytes.len();
while i + 8 <= len {
let w = u64::from_le_bytes(bytes[i..i + 8].try_into().unwrap());
let ge80 = w & HI;
let x = w ^ (LO * 0x7F);
let eq7f = x.wrapping_sub(LO) & !x & HI;
let stop = ge80 | eq7f | eq(w, 0x18) | eq(w, 0x1A) | eq(w, 0x1B);
if stop != 0 {
return i + (stop.trailing_zeros() as usize) / 8;
}
i += 8;
}
while i < len {
if matches!(bytes[i], 0x18 | 0x1A | 0x1B | 0x7F) || bytes[i] >= 0x80 {
return i;
}
i += 1;
}
len
}
const DECODE_CHUNK: usize = 4096;
#[cfg(not(target_arch = "wasm32"))]
const SIMD_DECODE_MIN: usize = 16;
fn maximal_subpart(p: &[u8]) -> usize {
if p.is_empty() {
return 0;
}
let b0 = p[0];
if !(0xC2..=0xF4).contains(&b0) {
return 1;
}
let (seq_len, lo, hi): (usize, [u8; 3], [u8; 3]) = match b0 {
0xC2..=0xDF => (2, [0x80, 0, 0], [0xBF, 0, 0]),
0xE0 => (3, [0xA0, 0x80, 0], [0xBF, 0xBF, 0]),
0xE1..=0xEC => (3, [0x80, 0x80, 0], [0xBF, 0xBF, 0]),
0xED => (3, [0x80, 0x80, 0], [0x9F, 0xBF, 0]),
0xEE..=0xEF => (3, [0x80, 0x80, 0], [0xBF, 0xBF, 0]),
0xF0 => (4, [0x90, 0x80, 0x80], [0xBF, 0xBF, 0xBF]),
0xF1..=0xF3 => (4, [0x80, 0x80, 0x80], [0xBF, 0xBF, 0xBF]),
0xF4 => (4, [0x80, 0x80, 0x80], [0x8F, 0xBF, 0xBF]),
_ => unreachable!(),
};
let mut valid = 1;
for i in 0..seq_len - 1 {
if valid >= p.len() {
break;
}
let cb = p[valid];
if cb < lo[i] || cb > hi[i] {
break;
}
valid += 1;
}
valid
}
fn trim_valid_partial_utf8(input: &[u8]) -> usize {
if input.is_empty() {
return 0;
}
let check_start = if input.len() > 4 { input.len() - 4 } else { 0 };
let mut pos = input.len();
while pos > check_start {
let b = input[pos - 1];
if (b & 0xC0) == 0x80 {
pos -= 1;
continue;
}
if !(0xC2..=0xF4).contains(&b) {
return input.len();
}
let expected = if b <= 0xDF {
2
} else if b <= 0xEF {
3
} else {
4
};
let seq_remaining = input.len() - (pos - 1);
if seq_remaining >= expected {
return input.len();
}
let seq_start = pos - 1;
let subpart = maximal_subpart(&input[seq_start..]);
if subpart == seq_remaining {
return seq_start;
}
return input.len();
}
input.len()
}
#[derive(PartialEq, Eq, Debug, Default, Copy, Clone)]
enum State {
CsiEntry,
CsiIgnore,
CsiIntermediate,
CsiParam,
DcsEntry,
DcsIgnore,
DcsIntermediate,
DcsParam,
DcsPassthrough,
Escape,
EscapeIntermediate,
OscString,
SosString,
ApcString,
PmString,
#[default]
Ground,
}
pub trait Perform {
fn print(&mut self, _c: char) {}
fn print_str(&mut self, s: &str) {
for c in s.chars() {
self.print(c);
}
}
fn print_codepoints(&mut self, codepoints: &[u32]) {
for &cp in codepoints {
let c = char::from_u32(cp).unwrap_or('\u{FFFD}');
self.print(c);
}
}
fn execute(&mut self, _byte: u8) {}
fn hook(
&mut self,
_params: &Params,
_intermediates: &[u8],
_ignore: bool,
_action: char,
) {
}
fn put(&mut self, _byte: u8) {}
fn put_slice(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.put(byte);
}
}
fn unhook(&mut self) {}
fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {}
fn csi_dispatch(
&mut self,
_params: &Params,
_intermediates: &[u8],
_ignore: bool,
_action: char,
) {
}
fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {}
fn sos_start(&mut self) {}
fn sos_put(&mut self, _byte: u8) {}
fn sos_put_slice(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.sos_put(byte);
}
}
fn sos_end(&mut self) {}
fn pm_start(&mut self) {}
fn pm_put(&mut self, _byte: u8) {}
fn pm_put_slice(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.pm_put(byte);
}
}
fn pm_end(&mut self) {}
fn apc_start(&mut self) {}
fn apc_put(&mut self, _byte: u8) {}
fn apc_put_slice(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.apc_put(byte);
}
}
fn apc_end(&mut self) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn scalar_and_simd_decode_agree() {
let invalid_cases: &[&[u8]] = &[
b"\x80", b"\xBF", b"\xE2\x96", b"\xF0\x9F\x92", b"\xC0\xAF", b"\xED\xA0\x80", b"\xF5\x80\x80\x80", b"a\xE2\x96b\x80c", "▀\u{45}\u{300}".as_bytes(), ];
for case in invalid_cases {
for pad in [0usize, SIMD_DECODE_MIN + 4] {
let mut input = case.to_vec();
input.extend(vec![b'x'; pad]);
let mut scalar = Parser::new();
scalar.decode_codepoints_scalar(&input);
let mut simd = Parser::new();
simd.decode_codepoints_simd(&input);
assert_eq!(
scalar.decode_buf, simd.decode_buf,
"decoder divergence on {case:?} pad {pad}",
);
}
}
}
const OSC_BYTES: &[u8] = &[
0x1B, 0x5D, b'2', b';', b'j', b'w', b'i', b'l', b'm', b'@', b'j', b'w', b'i', b'l', b'm',
b'-', b'd', b'e', b's', b'k', b':', b' ', b'~', b'/', b'c', b'o', b'd', b'e',
b'/', b'a', b'l', b'a', b'c', b'r', b'i', b't', b't', b'y', 0x07, ];
const ST_ESC_SEQUENCE: &[Sequence] = &[Sequence::Esc(vec![], false, 0x5C)];
#[derive(Default)]
struct Dispatcher {
dispatched: Vec<Sequence>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum OpaqueSequenceKind {
Sos,
Pm,
Apc,
}
#[test]
fn boundary_scanners_match_state_machine() {
for b in 0..=255u8 {
let mut d = Dispatcher::default();
let mut p = Parser::new();
p.advance(&mut d, b"\x1b]");
let before = p.osc_raw.len();
p.advance_osc_string(&mut d, b);
let put = p.osc_raw.len() > before && p.state == State::OscString;
assert_eq!(find_osc_boundary(&[b]) == 1, put, "osc byte {b:#04x}");
let wide = [b; 16];
assert_eq!(
find_osc_boundary(&wide) == 16,
put,
"osc swar byte {b:#04x}"
);
for kind in ["apc", "sos", "pm"] {
let mut d = Dispatcher::default();
let mut p = Parser::new();
match kind {
"apc" => p.advance(&mut d, b"\x1b_"),
"sos" => p.advance(&mut d, b"\x1bX"),
_ => p.advance(&mut d, b"\x1b^"),
}
let before = d.dispatched.len();
match kind {
"apc" => p.advance_apc_string(&mut d, b),
"sos" => p.advance_sos_string(&mut d, b),
_ => p.advance_pm_string(&mut d, b),
}
let events = &d.dispatched[before..];
let put = events.len() == 1
&& matches!(events[0], Sequence::OpaquePut(..))
&& p.state != State::Ground
&& p.state != State::Escape;
assert_eq!(find_string_c0(&[b]) == 1, put, "{kind} byte {b:#04x}");
assert_eq!(
find_string_c0(&[b; 16]) == 16,
put,
"{kind} swar byte {b:#04x}"
);
}
let mut d = Dispatcher::default();
let mut p = Parser::new();
p.advance(&mut d, b"\x1bPq");
let before = d.dispatched.len();
p.advance_dcs_passthrough(&mut d, b);
let events = &d.dispatched[before..];
let put = events.len() == 1
&& matches!(events[0], Sequence::DcsPut(_))
&& p.state == State::DcsPassthrough;
assert_eq!(find_dcs_boundary(&[b]) == 1, put, "dcs byte {b:#04x}");
assert_eq!(
find_dcs_boundary(&[b; 16]) == 16,
put,
"dcs swar byte {b:#04x}"
);
}
}
#[test]
fn string_state_runs_match_per_byte() {
let big = "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=".repeat(300);
let mut streams: Vec<Vec<u8>> = vec![
format!("\x1b]52;c;{big}\x07next").into_bytes(),
format!("\x1b]52;c;{big}\x1b\\next").into_bytes(),
b"\x1b]4;1;rgb:aa/bb/cc;2;rgb:dd/ee/ff\x07".to_vec(),
b"\x1b]0;title\x18after-can".to_vec(),
format!("\x1b_Gf=100,a=T;{big}\x1b\\tail").into_bytes(),
format!("\x1b_G{big}\x07").into_bytes(),
b"\x1b_Gpayload\x18rest".to_vec(),
b"\x1bP0;1q#0;2;0;0;0#1~~@@\x09data\x7fmore\x1b\\after".to_vec(),
b"\x1bPq~~\xc3\xa9~~\x9cafter".to_vec(),
b"\x1bPqdata\x1arest".to_vec(),
b"\x1bXsos payload 123\x1b\\g".to_vec(),
b"\x1b^pm payload 456\x07g".to_vec(),
format!("plain\x1b[31m\x1b]52;c;{big}\x07\x1b[0mtext").into_bytes(),
];
let mut soup = Vec::new();
for chunk in &streams {
soup.extend_from_slice(chunk);
}
streams.push(soup);
let mut lcg: u64 = 0x5EED_CAFE;
let mut rng = move |n: usize| {
lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
((lcg >> 33) as usize) % n
};
for stream in &streams {
let whole = {
let mut d = Dispatcher::default();
let mut p = Parser::new();
p.advance(&mut d, stream);
d.dispatched
};
let bytewise = {
let mut d = Dispatcher::default();
let mut p = Parser::new();
for &b in stream {
p.advance(&mut d, &[b]);
}
d.dispatched
};
assert_eq!(whole, bytewise, "whole vs per-byte");
for _round in 0..8 {
let mut d = Dispatcher::default();
let mut p = Parser::new();
let mut i = 0;
while i < stream.len() {
let n = 1 + rng(stream.len() - i);
p.advance(&mut d, &stream[i..i + n]);
i += n;
}
assert_eq!(d.dispatched, whole, "random splits vs whole");
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum Sequence {
Osc(Vec<Vec<u8>>, bool),
Csi(Vec<Vec<u16>>, Vec<u8>, bool, char),
Esc(Vec<u8>, bool, u8),
DcsHook(Vec<Vec<u16>>, Vec<u8>, bool, char),
DcsPut(u8),
Print(char),
Execute(u8),
OpaqueStart(OpaqueSequenceKind),
OpaquePut(OpaqueSequenceKind, u8),
OpaqueEnd(OpaqueSequenceKind),
DcsUnhook,
}
impl Perform for Dispatcher {
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
let params = params.iter().map(|p| p.to_vec()).collect();
self.dispatched.push(Sequence::Osc(params, bell_terminated));
}
fn csi_dispatch(
&mut self,
params: &Params,
intermediates: &[u8],
ignore: bool,
c: char,
) {
let params = params.iter().map(|subparam| subparam.to_vec()).collect();
let intermediates = intermediates.to_vec();
self.dispatched
.push(Sequence::Csi(params, intermediates, ignore, c));
}
fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
let intermediates = intermediates.to_vec();
self.dispatched
.push(Sequence::Esc(intermediates, ignore, byte));
}
fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) {
let params = params.iter().map(|subparam| subparam.to_vec()).collect();
let intermediates = intermediates.to_vec();
self.dispatched
.push(Sequence::DcsHook(params, intermediates, ignore, c));
}
fn put(&mut self, byte: u8) {
self.dispatched.push(Sequence::DcsPut(byte));
}
fn unhook(&mut self) {
self.dispatched.push(Sequence::DcsUnhook);
}
fn print(&mut self, c: char) {
self.dispatched.push(Sequence::Print(c));
}
fn execute(&mut self, byte: u8) {
self.dispatched.push(Sequence::Execute(byte));
}
fn sos_start(&mut self) {
self.dispatched
.push(Sequence::OpaqueStart(OpaqueSequenceKind::Sos));
}
fn sos_put(&mut self, byte: u8) {
self.dispatched
.push(Sequence::OpaquePut(OpaqueSequenceKind::Sos, byte));
}
fn sos_end(&mut self) {
self.dispatched
.push(Sequence::OpaqueEnd(OpaqueSequenceKind::Sos));
}
fn pm_start(&mut self) {
self.dispatched
.push(Sequence::OpaqueStart(OpaqueSequenceKind::Pm));
}
fn pm_put(&mut self, byte: u8) {
self.dispatched
.push(Sequence::OpaquePut(OpaqueSequenceKind::Pm, byte));
}
fn pm_end(&mut self) {
self.dispatched
.push(Sequence::OpaqueEnd(OpaqueSequenceKind::Pm));
}
fn apc_start(&mut self) {
self.dispatched
.push(Sequence::OpaqueStart(OpaqueSequenceKind::Apc));
}
fn apc_put(&mut self, byte: u8) {
self.dispatched
.push(Sequence::OpaquePut(OpaqueSequenceKind::Apc, byte));
}
fn apc_end(&mut self) {
self.dispatched
.push(Sequence::OpaqueEnd(OpaqueSequenceKind::Apc));
}
}
#[test]
fn parse_osc() {
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, OSC_BYTES);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Osc(params, _) => {
assert_eq!(params.len(), 2);
assert_eq!(params[0], &OSC_BYTES[2..3]);
assert_eq!(params[1], &OSC_BYTES[4..(OSC_BYTES.len() - 1)]);
}
_ => panic!("expected osc sequence"),
}
}
#[test]
fn parse_empty_osc() {
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, &[0x1B, 0x5D, 0x07]);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Osc(..) => (),
_ => panic!("expected osc sequence"),
}
}
#[test]
fn parse_osc_max_params() {
let params = ";".repeat(params::MAX_PARAMS + 1);
let input = format!("\x1b]{}\x1b", ¶ms[..]).into_bytes();
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, &input);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Osc(params, _) => {
assert_eq!(params.len(), MAX_OSC_PARAMS);
assert!(params.iter().all(Vec::is_empty));
}
_ => panic!("expected osc sequence"),
}
}
#[test]
fn osc_bell_terminated() {
const INPUT: &[u8] = b"\x1b]11;ff/00/ff\x07";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Osc(_, true) => (),
_ => panic!("expected osc with bell terminator"),
}
}
#[test]
fn osc_c0_st_terminated() {
const INPUT: &[u8] = b"\x1b]11;ff/00/ff\x1b\\";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 2);
match &dispatcher.dispatched[0] {
Sequence::Osc(_, false) => (),
_ => panic!("expected osc with ST terminator"),
}
}
#[test]
fn parse_osc_with_utf8_arguments() {
const INPUT: &[u8] = &[
0x0D, 0x1B, 0x5D, 0x32, 0x3B, 0x65, 0x63, 0x68, 0x6F, 0x20, 0x27, 0xC2, 0xAF,
0x5C, 0x5F, 0x28, 0xE3, 0x83, 0x84, 0x29, 0x5F, 0x2F, 0xC2, 0xAF, 0x27, 0x20,
0x26, 0x26, 0x20, 0x73, 0x6C, 0x65, 0x65, 0x70, 0x20, 0x31, 0x07,
];
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched[0], Sequence::Execute(b'\r'));
let osc_data = INPUT[5..(INPUT.len() - 1)].into();
assert_eq!(
dispatcher.dispatched[1],
Sequence::Osc(vec![vec![b'2'], osc_data], true)
);
assert_eq!(dispatcher.dispatched.len(), 2);
}
#[test]
fn osc_containing_string_terminator() {
const INPUT: &[u8] = b"\x1b]2;\xe6\x9c\xab\x1b\\";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 2);
match &dispatcher.dispatched[0] {
Sequence::Osc(params, _) => {
assert_eq!(params[1], &INPUT[4..(INPUT.len() - 2)]);
}
_ => panic!("expected osc sequence"),
}
}
#[test]
fn osc_fits_in_inline_buffer() {
const NUM_BYTES: usize = OSC_FIXED_LEN - 32;
const INPUT_START: &[u8] = b"\x1b]52;s";
const INPUT_END: &[u8] = b"\x07";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT_START);
parser.advance(&mut dispatcher, &[b'a'; NUM_BYTES]);
parser.advance(&mut dispatcher, INPUT_END);
assert!(parser.osc_raw.overflow.capacity() == 0);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Osc(params, _) => {
assert_eq!(params.len(), 2);
assert_eq!(params[0], b"52");
assert_eq!(params[1].len(), NUM_BYTES + INPUT_END.len());
}
_ => panic!("expected osc sequence"),
}
}
#[test]
fn osc_spills_to_overflow() {
const NUM_BYTES: usize = OSC_FIXED_LEN + 512;
const INPUT_START: &[u8] = b"\x1b]52;s";
const INPUT_END: &[u8] = b"\x07";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT_START);
parser.advance(&mut dispatcher, &[b'a'; NUM_BYTES]);
parser.advance(&mut dispatcher, INPUT_END);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Osc(params, _) => {
assert_eq!(params.len(), 2);
assert_eq!(params[0], b"52");
assert_eq!(params[1].len(), NUM_BYTES + INPUT_END.len());
assert_eq!(params[1][0], b's');
assert!(params[1][1..].iter().all(|&b| b == b'a'));
}
_ => panic!("expected osc sequence"),
}
}
#[test]
fn parse_csi_max_params() {
let params = "1;".repeat(params::MAX_PARAMS - 1);
let input = format!("\x1b[{}p", ¶ms[..]).into_bytes();
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, &input);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Csi(params, _, ignore, _) => {
assert_eq!(params.len(), params::MAX_PARAMS);
assert!(!ignore);
}
_ => panic!("expected csi sequence"),
}
}
#[test]
fn parse_csi_params_ignore_long_params() {
let params = "1;".repeat(params::MAX_PARAMS);
let input = format!("\x1b[{}p", ¶ms[..]).into_bytes();
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, &input);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Csi(params, _, ignore, _) => {
assert_eq!(params.len(), params::MAX_PARAMS);
assert!(ignore);
}
_ => panic!("expected csi sequence"),
}
}
#[test]
fn parse_csi_params_trailing_semicolon() {
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, b"\x1b[4;m");
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Csi(params, ..) => assert_eq!(params, &[[4], [0]]),
_ => panic!("expected csi sequence"),
}
}
#[test]
fn parse_csi_params_leading_semicolon() {
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, b"\x1b[;4m");
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Csi(params, ..) => assert_eq!(params, &[[0], [4]]),
_ => panic!("expected csi sequence"),
}
}
#[test]
fn parse_long_csi_param() {
const INPUT: &[u8] = b"\x1b[9223372036854775808m";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Csi(params, ..) => assert_eq!(params, &[[u16::MAX]]),
_ => panic!("expected csi sequence"),
}
}
#[test]
fn csi_reset() {
const INPUT: &[u8] = b"\x1b[3;1\x1b[?1049h";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Csi(params, intermediates, ignore, _) => {
assert_eq!(intermediates, b"?");
assert_eq!(params, &[[1049]]);
assert!(!ignore);
}
_ => panic!("expected csi sequence"),
}
}
#[test]
fn csi_subparameters() {
const INPUT: &[u8] = b"\x1b[38:2:255:0:255;1m";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Csi(params, intermediates, ignore, _) => {
assert_eq!(params, &[vec![38, 2, 255, 0, 255], vec![1]]);
assert!(intermediates.is_empty());
assert!(!ignore);
}
_ => panic!("expected csi sequence"),
}
}
#[test]
fn parse_dcs_max_params() {
let params = "1;".repeat(params::MAX_PARAMS + 1);
let input = format!("\x1bP{}p", ¶ms[..]).into_bytes();
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, &input);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::DcsHook(params, _, ignore, _) => {
assert_eq!(params.len(), params::MAX_PARAMS);
assert!(params.iter().all(|param| param == &[1]));
assert!(ignore);
}
_ => panic!("expected dcs sequence"),
}
}
#[test]
fn dcs_reset() {
const INPUT: &[u8] = b"\x1b[3;1\x1bP1$tx\x9c";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 3);
match &dispatcher.dispatched[0] {
Sequence::DcsHook(params, intermediates, ignore, _) => {
assert_eq!(intermediates, b"$");
assert_eq!(params, &[[1]]);
assert!(!ignore);
}
_ => panic!("expected dcs sequence"),
}
assert_eq!(dispatcher.dispatched[1], Sequence::DcsPut(b'x'));
assert_eq!(dispatcher.dispatched[2], Sequence::DcsUnhook);
}
#[test]
fn parse_dcs() {
const INPUT: &[u8] = b"\x1bP0;1|17/ab\x9c";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 7);
match &dispatcher.dispatched[0] {
Sequence::DcsHook(params, _, _, c) => {
assert_eq!(params, &[[0], [1]]);
assert_eq!(c, &'|');
}
_ => panic!("expected dcs sequence"),
}
for (i, byte) in b"17/ab".iter().enumerate() {
assert_eq!(dispatcher.dispatched[1 + i], Sequence::DcsPut(*byte));
}
assert_eq!(dispatcher.dispatched[6], Sequence::DcsUnhook);
}
#[test]
fn intermediate_reset_on_dcs_exit() {
const INPUT: &[u8] = b"\x1bP=1sZZZ\x1b+\x5c";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 6);
match &dispatcher.dispatched[5] {
Sequence::Esc(intermediates, ..) => assert_eq!(intermediates, b"+"),
_ => panic!("expected esc sequence"),
}
}
#[test]
fn esc_reset() {
const INPUT: &[u8] = b"\x1b[3;1\x1b(A";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Esc(intermediates, ignore, byte) => {
assert_eq!(intermediates, b"(");
assert_eq!(*byte, b'A');
assert!(!ignore);
}
_ => panic!("expected esc sequence"),
}
}
#[test]
fn esc_reset_intermediates() {
const INPUT: &[u8] = b"\x1b[?2004l\x1b#8";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 2);
assert_eq!(
dispatcher.dispatched[0],
Sequence::Csi(vec![vec![2004]], vec![63], false, 'l')
);
assert_eq!(dispatcher.dispatched[1], Sequence::Esc(vec![35], false, 56));
}
#[test]
fn params_buffer_filled_with_subparam() {
const INPUT: &[u8] = b"\x1b[::::::::::::::::::::::::::::::::x\x1b";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 1);
match &dispatcher.dispatched[0] {
Sequence::Csi(params, intermediates, ignore, c) => {
assert!(intermediates.is_empty());
assert_eq!(params, &[[0; 32]]);
assert_eq!(c, &'x');
assert!(ignore);
}
_ => panic!("expected csi sequence"),
}
}
fn expect_opaque_sequence(
input: &[u8],
kind: OpaqueSequenceKind,
expected_payload: &[u8],
expected_trailer: &[Sequence],
) {
let mut expected_dispatched: Vec<Sequence> = vec![Sequence::OpaqueStart(kind)];
for byte in expected_payload {
expected_dispatched.push(Sequence::OpaquePut(kind, *byte));
}
expected_dispatched.push(Sequence::OpaqueEnd(kind));
for item in expected_trailer {
expected_dispatched.push(item.clone());
}
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, input);
assert_eq!(dispatcher.dispatched, expected_dispatched);
}
#[test]
fn sos_c0_st_terminated() {
expect_opaque_sequence(
b"\x1bXTest\x20\xFF;xyz\x1b\\",
OpaqueSequenceKind::Sos,
b"Test\x20\xFF;xyz",
ST_ESC_SEQUENCE,
);
}
#[test]
fn sos_bell_terminated() {
expect_opaque_sequence(
b"\x1bXTest\x20\xFF;xyz\x07",
OpaqueSequenceKind::Sos,
b"Test\x20\xFF;xyz",
&[],
);
}
#[test]
fn sos_empty() {
expect_opaque_sequence(
b"\x1bX\x1b\\",
OpaqueSequenceKind::Sos,
&[],
ST_ESC_SEQUENCE,
);
}
#[test]
fn pm_c0_st_terminated() {
expect_opaque_sequence(
b"\x1b^Test\x20\xFF;xyz\x1b\\",
OpaqueSequenceKind::Pm,
b"Test\x20\xFF;xyz",
ST_ESC_SEQUENCE,
);
}
#[test]
fn pm_bell_terminated() {
expect_opaque_sequence(
b"\x1b^Test\x20\xFF;xyz\x07",
OpaqueSequenceKind::Pm,
b"Test\x20\xFF;xyz",
&[],
);
}
#[test]
fn pm_empty() {
expect_opaque_sequence(
b"\x1b^\x1b\\",
OpaqueSequenceKind::Pm,
&[],
ST_ESC_SEQUENCE,
);
}
#[test]
fn parse_kitty_apc() {
const INPUT: &[u8] = b"\x1b_Gf=24,s=10,v=20;Zm9v\x1b\\";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
let expected = vec![
Sequence::OpaqueStart(OpaqueSequenceKind::Apc),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'G'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'f'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'='),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'2'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'4'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b','),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b's'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'='),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'1'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'0'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b','),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'v'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'='),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'2'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'0'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b';'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'Z'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'm'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'9'),
Sequence::OpaquePut(OpaqueSequenceKind::Apc, b'v'),
Sequence::OpaqueEnd(OpaqueSequenceKind::Apc),
Sequence::Esc(vec![], false, b'\\'),
];
assert_eq!(dispatcher.dispatched, expected)
}
#[test]
fn parse_kitty_apc_dispatch_params() {
const INPUT: &[u8] = b"\x1b_Gf=32,s=10,v=20;AQIDBA==\x1b\\";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
let apc_dispatch = dispatcher
.dispatched
.iter()
.find(|s| matches!(s, Sequence::OpaqueEnd(OpaqueSequenceKind::Apc)));
assert!(apc_dispatch.is_some(), "Should have APC dispatch");
}
#[test]
fn apc_c0_st_terminated() {
expect_opaque_sequence(
b"\x1b_Test\x20\xFF;xyz\x1b\\",
OpaqueSequenceKind::Apc,
b"Test\x20\xFF;xyz",
ST_ESC_SEQUENCE,
);
}
#[test]
fn apc_bell_terminated() {
expect_opaque_sequence(
b"\x1b_Test\x20\xFF;xyz\x07",
OpaqueSequenceKind::Apc,
b"Test\x20\xFF;xyz",
&[],
);
}
#[test]
fn apc_empty() {
expect_opaque_sequence(
b"\x1b_\x1b\\",
OpaqueSequenceKind::Apc,
&[],
ST_ESC_SEQUENCE,
);
}
#[test]
fn unicode() {
const INPUT: &[u8] =
b"\xF0\x9F\x8E\x89_\xF0\x9F\xA6\x80\xF0\x9F\xA6\x80_\xF0\x9F\x8E\x89";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 6);
assert_eq!(dispatcher.dispatched[0], Sequence::Print('🎉'));
assert_eq!(dispatcher.dispatched[1], Sequence::Print('_'));
assert_eq!(dispatcher.dispatched[2], Sequence::Print('🦀'));
assert_eq!(dispatcher.dispatched[3], Sequence::Print('🦀'));
assert_eq!(dispatcher.dispatched[4], Sequence::Print('_'));
assert_eq!(dispatcher.dispatched[5], Sequence::Print('🎉'));
}
#[test]
fn invalid_utf8() {
const INPUT: &[u8] = b"a\xEF\xBCb";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 3);
assert_eq!(dispatcher.dispatched[0], Sequence::Print('a'));
assert_eq!(dispatcher.dispatched[1], Sequence::Print('�'));
assert_eq!(dispatcher.dispatched[2], Sequence::Print('b'));
}
#[test]
fn partial_utf8() {
const INPUT: &[u8] = b"\xF0\x9F\x9A\x80";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, &INPUT[..1]);
parser.advance(&mut dispatcher, &INPUT[1..2]);
parser.advance(&mut dispatcher, &INPUT[2..3]);
parser.advance(&mut dispatcher, &INPUT[3..]);
assert_eq!(dispatcher.dispatched.len(), 1);
assert_eq!(dispatcher.dispatched[0], Sequence::Print('🚀'));
}
#[test]
fn partial_utf8_separating_utf8() {
const INPUT: &[u8] = b"\xC4\xB8\xF0\x9F\x8E\x89";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, &INPUT[..1]);
parser.advance(&mut dispatcher, &INPUT[1..]);
assert_eq!(dispatcher.dispatched.len(), 2);
assert_eq!(dispatcher.dispatched[0], Sequence::Print('ĸ'));
assert_eq!(dispatcher.dispatched[1], Sequence::Print('🎉'));
}
#[test]
fn partial_invalid_utf8() {
const INPUT: &[u8] = b"a\xEF\xBCb";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, &INPUT[..1]);
parser.advance(&mut dispatcher, &INPUT[1..2]);
parser.advance(&mut dispatcher, &INPUT[2..3]);
parser.advance(&mut dispatcher, &INPUT[3..]);
assert_eq!(dispatcher.dispatched.len(), 3);
assert_eq!(dispatcher.dispatched[0], Sequence::Print('a'));
assert_eq!(dispatcher.dispatched[1], Sequence::Print('�'));
assert_eq!(dispatcher.dispatched[2], Sequence::Print('b'));
}
#[test]
fn partial_invalid_utf8_split() {
const INPUT: &[u8] = b"\xE4\xBF\x99\xB5";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, &INPUT[..2]);
parser.advance(&mut dispatcher, &INPUT[2..]);
assert_eq!(dispatcher.dispatched[0], Sequence::Print('俙'));
assert_eq!(dispatcher.dispatched[1], Sequence::Print('�'));
}
#[test]
fn partial_utf8_into_esc() {
const INPUT: &[u8] = b"\xD8\x1b012";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 4);
assert_eq!(dispatcher.dispatched[0], Sequence::Print('�'));
assert_eq!(
dispatcher.dispatched[1],
Sequence::Esc(Vec::new(), false, b'0')
);
assert_eq!(dispatcher.dispatched[2], Sequence::Print('1'));
assert_eq!(dispatcher.dispatched[3], Sequence::Print('2'));
}
#[test]
fn c1s() {
const INPUT: &[u8] = b"\x00\x1f\x80\x90\x98\x9b\x9c\x9d\x9e\x9fa";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 11);
assert_eq!(dispatcher.dispatched[0], Sequence::Execute(0));
assert_eq!(dispatcher.dispatched[1], Sequence::Execute(31));
assert_eq!(dispatcher.dispatched[2], Sequence::Execute(128));
assert_eq!(dispatcher.dispatched[3], Sequence::Execute(144));
assert_eq!(dispatcher.dispatched[4], Sequence::Execute(152));
assert_eq!(dispatcher.dispatched[5], Sequence::Execute(155));
assert_eq!(dispatcher.dispatched[6], Sequence::Execute(156));
assert_eq!(dispatcher.dispatched[7], Sequence::Execute(157));
assert_eq!(dispatcher.dispatched[8], Sequence::Execute(158));
assert_eq!(dispatcher.dispatched[9], Sequence::Execute(159));
assert_eq!(dispatcher.dispatched[10], Sequence::Print('a'));
}
#[test]
fn execute_anywhere() {
const INPUT: &[u8] = b"\x18\x1a";
let mut dispatcher = Dispatcher::default();
let mut parser = Parser::default();
parser.advance(&mut dispatcher, INPUT);
assert_eq!(dispatcher.dispatched.len(), 2);
assert_eq!(dispatcher.dispatched[0], Sequence::Execute(0x18));
assert_eq!(dispatcher.dispatched[1], Sequence::Execute(0x1A));
}
}