use core::mem::MaybeUninit;
use u8char::u8char;
pub struct VtMachine {
state: State,
intermediates: VtIntermediates,
params: VtParams,
in_literal_chunk: bool,
}
impl VtMachine {
pub const fn new() -> Self {
Self {
state: State::Literal,
intermediates: VtIntermediates::new(),
params: VtParams::new(),
in_literal_chunk: false,
}
}
pub fn write_u8char<'m>(&'m mut self, c: u8char) -> impl Iterator<Item = VtEvent<'m>> {
let fb = c.first_byte();
match fb {
b'\x18' | b'\x1a' | b'\x80'..=b'\x8f' | b'\x91'..=b'\x97' | b'\x99' | b'\x9a' => {
return self.change_state(State::Literal, Action::Execute, c);
}
b'\x9c' => {
return self.change_state(State::Literal, Action::None, c);
}
b'\x1b' => {
return self.change_state(State::Escape, Action::None, c);
}
b'\x98' | b'\x9e' | b'\x9f' => {
return self.change_state(State::IgnoreUntilSt, Action::None, c);
}
b'\x90' => {
return self.change_state(State::DevCtrlStart, Action::None, c);
}
b'\x9d' => {
return self.change_state(State::OsCmd, Action::None, c);
}
b'\x9b' => {
return self.change_state(State::CtrlStart, Action::None, c);
}
_ => {
}
}
match self.state {
State::Literal => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' => {
return self.just_action(Action::Execute, c);
}
_ => return self.just_action(Action::Print, c),
},
State::Escape => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' => {
return self.just_action(Action::Execute, c);
}
b'\x7f' => {
return self.no_change(); }
b'\x20'..=b'\x2f' => {
return self.change_state(State::EscapeIntermediate, Action::Collect, c);
}
b'\x30'..=b'\x4f'
| b'\x51'..=b'\x57'
| b'\x59'
| b'\x5a'
| b'\x5c'
| b'\x60'..=b'\x7e' => {
return self.change_state(State::Literal, Action::EscDispatch, c);
}
b'\x5b' => {
return self.change_state(State::CtrlStart, Action::None, c);
}
b'\x5d' => {
return self.change_state(State::OsCmd, Action::None, c);
}
b'\x50' => {
return self.change_state(State::DevCtrlStart, Action::None, c);
}
b'\x58' | b'\x5e' | b'\x5f' => {
return self.change_state(State::IgnoreUntilSt, Action::None, c);
}
_ => return self.error(c),
},
State::EscapeIntermediate => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' => {
return self.just_action(Action::Execute, c);
}
b'\x7f' => {
return self.no_change(); }
b'\x20'..=b'\x2f' => {
return self.just_action(Action::Collect, c);
}
b'\x30'..=b'\x7e' => {
return self.change_state(State::Literal, Action::EscDispatch, c);
}
_ => return self.error(c),
},
State::CtrlStart => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' => {
return self.just_action(Action::Execute, c);
}
b'\x7f' => {
return self.no_change(); }
b'\x20'..=b'\x2f' => {
return self.change_state(State::CtrlIntermediate, Action::Collect, c);
}
b'\x3a' => {
return self.change_state(State::CtrlMalformed, Action::None, c);
}
b'\x30'..=b'\x39' | b'\x3b' => {
return self.change_state(State::CtrlParam, Action::Param, c);
}
b'\x3c'..=b'\x3f' => {
return self.change_state(State::CtrlParam, Action::Collect, c);
}
b'\x40'..=b'\x7e' => {
return self.change_state(State::Literal, Action::CsiDispatch, c);
}
_ => return self.error(c),
},
State::CtrlParam => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' => {
return self.just_action(Action::Execute, c);
}
b'\x30'..=b'\x39' | b'\x3b' => {
return self.just_action(Action::Param, c);
}
b'\x7f' => {
return self.no_change(); }
b'\x3a' | b'\x3c'..=b'\x3f' => {
return self.change_state(State::CtrlMalformed, Action::None, c);
}
b'\x20'..=b'\x2f' => {
return self.change_state(State::CtrlIntermediate, Action::Collect, c);
}
b'\x40'..=b'\x7e' => {
return self.change_state(State::Literal, Action::CsiDispatch, c);
}
_ => return self.error(c),
},
State::CtrlIntermediate => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' => {
return self.just_action(Action::Execute, c);
}
b'\x20'..=b'\x2f' => {
return self.just_action(Action::Collect, c);
}
b'\x7f' => {
return self.no_change(); }
b'\x3a' | b'\x3c'..=b'\x3f' => {
return self.change_state(State::CtrlMalformed, Action::None, c);
}
b'\x40'..=b'\x7e' => {
return self.change_state(State::Literal, Action::CsiDispatch, c);
}
_ => return self.error(c),
},
State::CtrlMalformed => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' => {
return self.just_action(Action::Execute, c);
}
b'\x20'..=b'\x3f' | b'\x7f' => {
return self.no_change(); }
b'\x40'..=b'\x7e' => {
return self.change_state(State::Literal, Action::None, c);
}
_ => return self.error(c),
},
State::DevCtrlStart => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' | b'\x7f' => {
return self.no_change(); }
b'\x3a' => {
return self.change_state(State::DevCtrlMalformed, Action::None, c);
}
b'\x20'..=b'\x2f' => {
return self.change_state(State::DevCtrlIntermediate, Action::Collect, c);
}
b'\x30'..=b'\x39' | b'\x3b' => {
return self.change_state(State::DevCtrlParam, Action::Param, c);
}
b'\x3c'..=b'\x3f' => {
return self.change_state(State::DevCtrlParam, Action::Collect, c);
}
b'\x40'..=b'\x7e' => {
return self.change_state(State::DevCtrlPassthru, Action::None, c);
}
_ => return self.error(c),
},
State::DevCtrlParam => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' | b'\x7f' => {
return self.no_change(); }
b'\x30'..=b'\x39' | b'\x3b' => {
return self.just_action(Action::Param, c);
}
b'\x3a' | b'\x3c'..=b'\x3f' => {
return self.change_state(State::DevCtrlMalformed, Action::None, c);
}
b'\x20'..=b'\x2f' => {
return self.change_state(State::DevCtrlIntermediate, Action::Collect, c);
}
b'\x40'..=b'\x7e' => {
return self.change_state(State::DevCtrlPassthru, Action::None, c);
}
_ => return self.error(c),
},
State::DevCtrlIntermediate => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' | b'\x7f' => {
return self.no_change(); }
b'\x20'..=b'\x2f' => {
return self.just_action(Action::Collect, c);
}
b'\x30'..=b'\x3f' => {
return self.change_state(State::DevCtrlMalformed, Action::None, c);
}
b'\x40'..=b'\x7e' => {
return self.change_state(State::DevCtrlPassthru, Action::None, c);
}
_ => return self.error(c),
},
State::DevCtrlPassthru => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' | b'\x20'..=b'\x7e' => {
return self.just_action(Action::Put, c);
}
b'\x7f' => {
return self.no_change(); }
_ => return self.error(c),
},
State::DevCtrlMalformed => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' | b'\x20'..=b'\x7f' => {
return self.no_change(); }
_ => return self.error(c),
},
State::OsCmd => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' => {
return self.no_change(); }
b'\x20'..=b'\x7f' => {
return self.just_action(Action::OscPut, c);
}
_ => return self.error(c),
},
State::IgnoreUntilSt => match fb {
b'\x00'..=b'\x17' | b'\x19' | b'\x1c'..=b'\x1f' | b'\x20'..=b'\x7f' => {
return self.no_change(); }
_ => return self.error(c),
},
}
}
pub fn write_char<'m>(&'m mut self, c: char) -> impl Iterator<Item = VtEvent<'m>> {
self.write_u8char(u8char::from_char(c))
}
pub fn write_end(&mut self) -> impl Iterator<Item = VtEvent<'static>> {
self.state = State::Literal;
self.intermediates.clear();
self.params.clear();
let event = if self.in_literal_chunk {
self.in_literal_chunk = false;
Some(VtEvent::PrintEnd)
} else {
None
};
Transition::new([event])
}
fn action(&mut self, action: Action, c: u8char) -> Option<VtEvent<'static>> {
match action {
Action::Collect => self.intermediates.push(c.first_byte()),
Action::Param => {
self.params.push_csi_char(c);
}
Action::Clear | Action::Error => {
self.intermediates.clear();
self.params.clear();
}
Action::Print => {}
Action::Execute => {}
Action::Hook => {}
Action::Put => {}
Action::OscStart => {}
Action::OscPut => {}
Action::CsiDispatch => {}
Action::EscDispatch => {}
Action::None => {}
}
if matches!(action, Action::Print) {
self.in_literal_chunk = true;
} else if self.in_literal_chunk {
self.in_literal_chunk = false;
return Some(VtEvent::PrintEnd);
}
None
}
fn action_event<'m>(&'m self, action: Action, c: u8char) -> Option<VtEvent<'m>> {
match action {
Action::Print => Some(VtEvent::Print(c)),
Action::Execute => Some(VtEvent::ExecuteCtrl(c.first_byte())),
Action::Hook => Some(VtEvent::DcsStart {
cmd: c.first_byte(),
params: &self.params.values(),
intermediates: &self.intermediates.chars(),
}),
Action::Put => Some(VtEvent::DcsChar(c)),
Action::OscStart => Some(VtEvent::OscStart(c.first_byte())),
Action::OscPut => Some(VtEvent::OscChar(c)),
Action::CsiDispatch => Some(VtEvent::DispatchCsi {
cmd: c.first_byte(),
params: &self.params.values(),
intermediates: &self.intermediates.chars(),
}),
Action::EscDispatch => Some(VtEvent::DispatchEsc {
cmd: c.first_byte(),
intermediates: &self.intermediates.chars(),
}),
Action::None => None,
Action::Collect => None,
Action::Param => None,
Action::Clear => None,
Action::Error => Some(VtEvent::Error(c)),
}
}
fn just_action<'m>(&'m mut self, action: Action, c: u8char) -> Transition<'m> {
let main_cleanup_event = self.action(action, c);
let main_event = self.action_event(action, c);
Transition::new([main_cleanup_event, main_event])
}
fn no_change(&self) -> Transition<'static> {
Transition::new([])
}
fn change_state<'m>(
&'m mut self,
state: State,
transition: Action,
c: u8char,
) -> Transition<'m> {
let exit_event = self.state_exit_event(self.state, c);
self.state = state;
let entry_action = self.state_entry_action(state);
let entry_cleanup_event = if let Some(action) = entry_action {
self.action(action, c)
} else {
None
};
let main_cleanup_event = self.action(transition, c);
let entry_event = if let Some(action) = entry_action {
self.action_event(action, c)
} else {
None
};
let main_event = self.action_event(transition, c);
Transition::new([
exit_event,
entry_cleanup_event,
main_cleanup_event,
main_event,
entry_event,
])
}
fn state_entry_action(&mut self, state: State) -> Option<Action> {
match state {
State::Escape => Some(Action::Clear),
State::CtrlStart => Some(Action::Clear),
State::DevCtrlStart => Some(Action::Clear),
State::OsCmd => Some(Action::OscStart),
State::DevCtrlPassthru => Some(Action::Hook),
_ => None,
}
}
fn state_exit_event(&mut self, state: State, c: u8char) -> Option<VtEvent<'static>> {
match state {
State::OsCmd => Some(VtEvent::OscEnd(c.first_byte())),
State::DevCtrlPassthru => Some(VtEvent::DcsEnd(c.first_byte())),
_ => None,
}
}
fn error<'m>(&'m mut self, c: u8char) -> Transition<'m> {
self.change_state(State::Literal, Action::Error, c)
}
}
struct Transition<'m> {
next: usize,
events: [MaybeUninit<VtEvent<'m>>; 5],
}
impl<'m> Transition<'m> {
#[inline(always)]
pub fn new<const N: usize>(events: [Option<VtEvent<'m>>; N]) -> Self {
assert!(const { N <= 5 });
let mut ret = Self {
next: 5,
events: [MaybeUninit::uninit(); 5],
};
for maybe_event in events.iter().rev() {
if let Some(event) = maybe_event {
ret.next -= 1;
ret.events[ret.next].write(*event);
}
}
ret
}
}
impl<'m> Iterator for Transition<'m> {
type Item = VtEvent<'m>;
fn next(&mut self) -> Option<Self::Item> {
if self.next == self.events.len() {
return None;
}
let ret = unsafe { self.events[self.next].assume_init() };
self.next += 1;
Some(ret)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VtEvent<'m> {
Print(u8char),
PrintEnd,
ExecuteCtrl(u8),
DispatchCsi {
cmd: u8,
params: &'m [u16],
intermediates: &'m [u8],
},
DispatchEsc {
cmd: u8,
intermediates: &'m [u8],
},
DcsStart {
cmd: u8,
params: &'m [u16],
intermediates: &'m [u8],
},
DcsChar(u8char),
DcsEnd(u8),
OscStart(u8),
OscChar(u8char),
OscEnd(u8),
Error(u8char),
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Action {
Print,
Execute,
Hook,
Put,
OscStart,
OscPut,
CsiDispatch,
EscDispatch,
None,
Collect,
Param,
Clear,
Error,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum State {
Literal,
Escape,
EscapeIntermediate,
CtrlStart,
CtrlParam,
CtrlIntermediate,
CtrlMalformed,
DevCtrlStart,
DevCtrlParam,
DevCtrlIntermediate,
DevCtrlPassthru,
DevCtrlMalformed,
OsCmd,
IgnoreUntilSt,
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct VtParams {
buf: [u16; 16],
len: u8,
}
impl VtParams {
pub const fn new() -> Self {
Self {
buf: [0; 16],
len: 0,
}
}
pub fn push(&mut self, v: u16) {
if (self.len as usize) == self.buf.len() {
return; }
self.buf[self.len as usize] = v;
self.len += 1;
}
fn push_csi_char(&mut self, c: u8char) {
if c.first_byte() == b';' {
self.push(0);
} else {
if self.len == 0 {
self.push(0); }
let current = &mut self.buf[(self.len as usize) - 1];
let digit = (c.to_char() as u16) - ('0' as u16);
*current *= 10;
*current += digit;
}
}
#[inline(always)]
pub fn clear(&mut self) {
self.len = 0;
}
#[inline(always)]
pub fn values(&self) -> &[u16] {
&self.buf[..(self.len as usize)]
}
}
impl core::fmt::Debug for VtParams {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("VtParams")
.field(&&self.buf[..(self.len as usize)])
.finish()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct VtIntermediates {
buf: [u8; 2],
len: u8, }
impl VtIntermediates {
const OVERRUN_LEN: usize = 3;
pub const fn new() -> Self {
Self {
buf: [0; 2],
len: 0,
}
}
pub fn push(&mut self, c: u8) {
let len = self.len();
if len >= self.buf.len() {
self.len = Self::OVERRUN_LEN as u8;
return;
}
self.buf[len] = c;
self.len += 1;
}
#[inline(always)]
pub fn clear(&mut self) {
self.len = 0;
}
pub fn chars(&self) -> &[u8] {
let len = self.len();
&self.buf[..len]
}
#[inline(always)]
pub fn len(&self) -> usize {
core::cmp::min(self.buf.len(), self.len as usize)
}
}
impl core::fmt::Debug for VtIntermediates {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("VtIntermediates")
.field(&&self.buf[..(self.len as usize)])
.finish()
}
}