#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Token {
Text(String),
Control(u8),
Csi {
private: Option<char>,
params: Vec<u16>,
intermediates: Vec<u8>,
final_byte: char,
},
Esc {
intermediates: Vec<u8>,
final_byte: u8,
},
Osc(String),
Other { intro: u8, data: Vec<u8> },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
Ground,
Escape,
EscapeIntermediate,
Csi,
Osc,
Str, }
#[derive(Debug, Default)]
pub struct Parser {
state: Option<State>, text: String,
utf8: Vec<u8>,
intermediates: Vec<u8>,
param_bytes: Vec<u8>,
private: Option<char>,
str_intro: u8,
str_data: Vec<u8>,
str_esc: bool,
}
const MAX_SEQ: usize = 4096;
impl Parser {
pub fn new() -> Self {
Self::default()
}
fn state(&self) -> State {
self.state.unwrap_or(State::Ground)
}
pub fn feed(&mut self, input: &[u8]) -> Vec<Token> {
let mut out = Vec::new();
for &b in input {
self.byte(b, &mut out);
}
self.flush_text(&mut out);
out
}
fn flush_text(&mut self, out: &mut Vec<Token>) {
if !self.text.is_empty() {
out.push(Token::Text(std::mem::take(&mut self.text)));
}
}
fn start(&mut self, state: State) {
self.state = Some(state);
self.intermediates.clear();
self.param_bytes.clear();
self.private = None;
self.str_data.clear();
self.str_esc = false;
}
fn byte(&mut self, b: u8, out: &mut Vec<Token>) {
match self.state() {
State::Ground => self.ground(b, out),
State::Escape => self.escape(b, out),
State::EscapeIntermediate => self.escape_intermediate(b, out),
State::Csi => self.csi(b, out),
State::Osc => self.string_byte(b, true, out),
State::Str => self.string_byte(b, false, out),
}
}
fn ground(&mut self, b: u8, out: &mut Vec<Token>) {
if !self.utf8.is_empty() {
if (0x80..0xC0).contains(&b) {
self.utf8.push(b);
let want = utf8_len(self.utf8[0]);
if self.utf8.len() == want {
match std::str::from_utf8(&self.utf8) {
Ok(s) => self.text.push_str(s),
Err(_) => self.text.push('\u{FFFD}'),
}
self.utf8.clear();
}
return;
}
self.text.push('\u{FFFD}');
self.utf8.clear();
}
match b {
0x1B => {
self.flush_text(out);
self.start(State::Escape);
}
0x00..=0x1F | 0x7F => {
self.flush_text(out);
out.push(Token::Control(b));
}
0x20..=0x7E => self.text.push(b as char),
0x80..=0xBF => self.text.push('\u{FFFD}'), _ => {
if utf8_len(b) == 1 {
self.text.push('\u{FFFD}'); } else {
self.utf8.push(b);
}
}
}
}
fn escape(&mut self, b: u8, out: &mut Vec<Token>) {
match b {
b'[' => self.start(State::Csi),
b']' => self.start(State::Osc),
b'P' | b'X' | b'^' | b'_' => {
self.start(State::Str);
self.str_intro = b;
}
0x20..=0x2F => {
self.intermediates.push(b);
self.state = Some(State::EscapeIntermediate);
}
0x30..=0x7E => {
out.push(Token::Esc {
intermediates: Vec::new(),
final_byte: b,
});
self.state = None;
}
0x1B => self.start(State::Escape),
0x18 | 0x1A => self.state = None, _ => out.push(Token::Control(b)), }
}
fn escape_intermediate(&mut self, b: u8, out: &mut Vec<Token>) {
match b {
0x20..=0x2F => {
if self.intermediates.len() < 8 {
self.intermediates.push(b);
}
}
0x30..=0x7E => {
out.push(Token::Esc {
intermediates: std::mem::take(&mut self.intermediates),
final_byte: b,
});
self.state = None;
}
0x1B => self.start(State::Escape),
0x18 | 0x1A => self.state = None,
_ => out.push(Token::Control(b)),
}
}
fn csi(&mut self, b: u8, out: &mut Vec<Token>) {
match b {
b'0'..=b'9' | b';' | b':' => {
if self.param_bytes.len() < MAX_SEQ {
self.param_bytes.push(b);
} else {
self.state = None; }
}
0x3C..=0x3F => {
if self.private.is_none() && self.param_bytes.is_empty() {
self.private = Some(b as char);
} }
0x20..=0x2F => {
if self.intermediates.len() < 8 {
self.intermediates.push(b);
}
}
0x40..=0x7E => {
out.push(Token::Csi {
private: self.private.take(),
params: parse_params(&self.param_bytes),
intermediates: std::mem::take(&mut self.intermediates),
final_byte: b as char,
});
self.state = None;
}
0x1B => self.start(State::Escape),
0x18 | 0x1A => self.state = None,
_ => out.push(Token::Control(b)),
}
}
fn string_byte(&mut self, b: u8, is_osc: bool, out: &mut Vec<Token>) {
if self.str_esc {
self.str_esc = false;
if b == b'\\' {
self.dispatch_string(is_osc, out);
return;
}
if self.str_data.len() < MAX_SEQ {
self.str_data.push(0x1B);
}
}
match b {
0x07 => self.dispatch_string(is_osc, out), 0x1B => self.str_esc = true,
_ => {
if self.str_data.len() < MAX_SEQ {
self.str_data.push(b);
}
}
}
}
fn dispatch_string(&mut self, is_osc: bool, out: &mut Vec<Token>) {
let data = std::mem::take(&mut self.str_data);
if is_osc {
out.push(Token::Osc(String::from_utf8_lossy(&data).into_owned()));
} else {
out.push(Token::Other {
intro: self.str_intro,
data,
});
}
self.state = None;
}
}
fn utf8_len(lead: u8) -> usize {
match lead {
0xC0..=0xDF => 2,
0xE0..=0xEF => 3,
0xF0..=0xF7 => 4,
_ => 1,
}
}
fn parse_params(bytes: &[u8]) -> Vec<u16> {
if bytes.is_empty() {
return Vec::new();
}
bytes
.split(|&b| b == b';')
.take(32)
.map(|seg| {
let mut v: u32 = 0;
for &b in seg {
if !b.is_ascii_digit() {
break;
}
v = (v * 10 + (b - b'0') as u32).min(u16::MAX as u32);
}
v as u16
})
.collect()
}