term-parser 0.2.0

ANSI escape code parser
Documentation
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u8)]
pub enum State {
	CsiEntry = 0,
	CsiIgnore = 1,
	CsiIntermediate = 2,
	CsiParam = 3,
	DcsEntry = 4,
	DcsIgnore = 5,
	DcsIntermediate = 6,
	DcsParam = 7,
	DcsPassthrough = 8,
	Error = 9,
	Escape = 10,
	EscapeIntermediate = 11,
	Ground = 12,
	OscString = 13,
	SosPmApcString = 14,
}

static STATES: &'static [State] = &[
	State::CsiEntry,
	State::CsiIgnore,
	State::CsiIntermediate,
	State::CsiParam,
	State::DcsEntry,
	State::DcsIgnore,
	State::DcsIntermediate,
	State::DcsParam,
	State::DcsPassthrough,
	State::Error,
	State::Escape,
	State::EscapeIntermediate,
	State::Ground,
	State::OscString,
	State::SosPmApcString,
];

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u8)]
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,
}

pub static ENTRY_ACTIONS: &'static [Action] = &[
	Action::Clear,    // State::CsiEntry
	Action::None,     // State::CsiIgnore
	Action::None,     // State::CsiIntermediate
	Action::None,     // State::CsiParam
	Action::Clear,    // State::DcsEntry
	Action::None,     // State::DcsIgnore
	Action::None,     // State::DcsIntermediate
	Action::None,     // State::DcsParam
	Action::Hook,     // State::DcsPassthrough
	Action::None,     // State::Error
	Action::Clear,    // State::Escape
	Action::None,     // State::EscapeIntermediate
	Action::None,     // State::Ground
	Action::OscStart, // State::OscString
	Action::None,     // State::SosPmApcString
];

pub static EXIT_ACTIONS: &'static [Action] = &[
	Action::None,   // State::CsiEntry
	Action::None,   // State::CsiIgnore
	Action::None,   // State::CsiIntermediate
	Action::None,   // State::CsiParam
	Action::None,   // State::DcsEntry
	Action::None,   // State::DcsIgnore
	Action::None,   // State::DcsIntermediate
	Action::None,   // State::DcsParam
	Action::Unhook, // State::DcsPassthrough
	Action::None,   // State::Error
	Action::None,   // State::Escape
	Action::None,   // State::EscapeIntermediate
	Action::None,   // State::Ground
	Action::OscEnd, // State::OscString
	Action::None,   // State::SosPmApcString
];

#[rustfmt::skip]
pub static UTF8_CHAR_WIDTH: [u8; 256] = [
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x0F
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x1F
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x2F
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x3F
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x4F
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x5F
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x6F
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x7F
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x8F
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9F
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xAF
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBF
	0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xCF
	2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDF
	3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEF
	4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0, // 0xFF
];

// https://vt100.net/emu/dec_ansi_parser
fn state_table_fn(state: State, byte: u8) -> (Action, State) {
	match byte {
		0x18 => return (Action::Execute, State::Ground),
		0x1A => return (Action::Execute, State::Ground),
		0x80...0x8F => return (Action::Execute, State::Ground),
		0x91...0x97 => return (Action::Execute, State::Ground),
		0x99 => return (Action::Execute, State::Ground),
		0x9A => return (Action::Execute, State::Ground),

		0x9C => return (Action::None, State::Ground),
		0x1B => return (Action::None, State::Escape),

		0x98 => return (Action::None, State::SosPmApcString),
		0x9E => return (Action::None, State::SosPmApcString),
		0x9F => return (Action::None, State::SosPmApcString),

		0x90 => return (Action::None, State::DcsEntry),
		0x9D => return (Action::None, State::OscString),
		0x9B => return (Action::None, State::CsiEntry),

		_ => {}
	};
	match state {
		State::CsiEntry => match byte {
			0x00...0x17 => (Action::Execute, State::CsiEntry),
			0x19 => (Action::Execute, State::CsiEntry),
			0x1C...0x1F => (Action::Execute, State::CsiEntry),
			0x7F => (Action::Ignore, State::CsiEntry),

			0x30...0x39 => (Action::Param, State::CsiParam),
			0x3C...0x3F => (Action::Collect, State::CsiParam),

			0x3A => (Action::None, State::CsiIgnore),

			0x20...0x2F => (Action::Collect, State::CsiIntermediate),

			0x40...0x7E => (Action::CsiDispatch, State::Ground),

			_ => (Action::None, State::Error),
		},
		State::CsiIgnore => match byte {
			0x00...0x17 => (Action::Execute, State::CsiIgnore),
			0x19 => (Action::Execute, State::CsiIgnore),
			0x1C...0x1F => (Action::Execute, State::CsiIgnore),
			0x20...0x3F => (Action::Ignore, State::CsiIgnore),
			0x7F => (Action::Ignore, State::CsiIgnore),

			0x40...0x7E => (Action::None, State::Ground),

			_ => (Action::None, State::Error),
		},
		State::CsiIntermediate => match byte {
			0x00...0x17 => (Action::Execute, State::CsiIntermediate),
			0x19 => (Action::Execute, State::CsiIntermediate),
			0x1C...0x1F => (Action::Execute, State::CsiIntermediate),
			0x20...0x2F => (Action::Collect, State::CsiIntermediate),
			0x7F => (Action::Ignore, State::CsiIntermediate),

			0x40...0x7E => (Action::CsiDispatch, State::Ground),

			_ => (Action::None, State::Error),
		},
		State::CsiParam => match byte {
			0x00...0x17 => (Action::Execute, State::CsiParam),
			0x19 => (Action::Execute, State::CsiParam),
			0x1C...0x1F => (Action::Execute, State::CsiParam),
			0x30...0x39 => (Action::Param, State::CsiParam),
			0x3B => (Action::Param, State::CsiParam),
			0x7F => (Action::Ignore, State::CsiParam),

			0x40...0x7E => (Action::CsiDispatch, State::Ground),

			0x20...0x2F => (Action::Collect, State::CsiIntermediate),

			0x3A => (Action::None, State::CsiIgnore),
			0x3C...0x3F => (Action::None, State::CsiIgnore),

			_ => (Action::None, State::Error),
		},
		State::DcsEntry => match byte {
			0x00...0x17 => (Action::Ignore, State::DcsEntry),
			0x19 => (Action::Ignore, State::DcsEntry),
			0x1C...0x1F => (Action::Ignore, State::DcsEntry),
			0x7F => (Action::Ignore, State::DcsEntry),

			0x20...0x2F => (Action::Collect, State::DcsIntermediate),

			0x3A => (Action::None, State::DcsIgnore),

			0x30...0x39 => (Action::Param, State::DcsParam),
			0x3C...0x3F => (Action::Collect, State::DcsParam),

			0x40...0x7E => (Action::None, State::DcsPassthrough),

			_ => (Action::None, State::Error),
		},
		State::DcsIgnore => match byte {
			0x00...0x17 => (Action::Ignore, State::DcsIgnore),
			0x19 => (Action::Ignore, State::DcsIgnore),
			0x1C...0x1F => (Action::Ignore, State::DcsIgnore),
			0x20...0x7F => (Action::Ignore, State::DcsIgnore),

			_ => (Action::None, State::Error),
		},
		State::DcsIntermediate => match byte {
			0x00...0x17 => (Action::Ignore, State::DcsIntermediate),
			0x19 => (Action::Ignore, State::DcsIntermediate),
			0x1C...0x1F => (Action::Ignore, State::DcsIntermediate),
			0x20...0x2F => (Action::Collect, State::DcsIntermediate),
			0x7F => (Action::Ignore, State::DcsIntermediate),

			0x40...0x7E => (Action::None, State::DcsPassthrough),

			0x30...0x3F => (Action::None, State::DcsIgnore),

			_ => (Action::None, State::Error),
		},
		State::DcsParam => match byte {
			0x00...0x17 => (Action::Ignore, State::DcsParam),
			0x19 => (Action::Ignore, State::DcsParam),
			0x1C...0x1F => (Action::Ignore, State::DcsParam),
			0x30...0x39 => (Action::Param, State::DcsParam),
			0x3B => (Action::Param, State::DcsParam),
			0x7F => (Action::Ignore, State::DcsParam),

			0x3A => (Action::None, State::DcsIgnore),
			0x3C...0x3F => (Action::None, State::DcsIgnore),

			0x20...0x2F => (Action::Collect, State::DcsIntermediate),

			0x40...0x7E => (Action::None, State::DcsPassthrough),

			_ => (Action::None, State::Error),
		},
		State::DcsPassthrough => match byte {
			0x00...0x17 => (Action::Put, State::DcsPassthrough),
			0x19 => (Action::Put, State::DcsPassthrough),
			0x1C...0x1F => (Action::Put, State::DcsPassthrough),
			0x20...0x7E => (Action::Put, State::DcsPassthrough),
			0x7F => (Action::Ignore, State::DcsPassthrough),

			_ => (Action::None, State::Error),
		},
		State::Error => (Action::None, State::Error),
		State::Escape => match byte {
			0x00...0x17 => (Action::Execute, State::Escape),
			0x19 => (Action::Execute, State::Escape),
			0x1C...0x1F => (Action::Execute, State::Escape),
			0x7F => (Action::Ignore, State::Escape),

			0x20...0x2F => (Action::Collect, State::EscapeIntermediate),

			0x30...0x4F => (Action::EscDispatch, State::Ground),
			0x51...0x57 => (Action::EscDispatch, State::Ground),
			0x59 => (Action::EscDispatch, State::Ground),
			0x5A => (Action::EscDispatch, State::Ground),
			0x5C => (Action::EscDispatch, State::Ground),
			0x60...0x7E => (Action::EscDispatch, State::Ground),

			0x5B => (Action::None, State::CsiEntry),

			0x5D => (Action::None, State::OscString),

			0x50 => (Action::None, State::DcsEntry),

			0x58 => (Action::None, State::SosPmApcString),
			0x5E => (Action::None, State::SosPmApcString),
			0x5F => (Action::None, State::SosPmApcString),

			_ => (Action::None, State::Error),
		},
		State::EscapeIntermediate => match byte {
			0x00...0x17 => (Action::Execute, State::EscapeIntermediate),
			0x19 => (Action::Execute, State::EscapeIntermediate),
			0x1C...0x1F => (Action::Execute, State::EscapeIntermediate),
			0x20...0x2F => (Action::Collect, State::EscapeIntermediate),
			0x7F => (Action::Ignore, State::EscapeIntermediate),

			0x30...0x7E => (Action::EscDispatch, State::Ground),

			_ => (Action::None, State::Error),
		},
		State::Ground => match byte {
			0x00...0x17 => (Action::Execute, State::Ground),
			0x19 => (Action::Execute, State::Ground),
			0x1C...0x1F => (Action::Execute, State::Ground),

			// UTF-8 1 byte code point
			0x20...0x7F => (Action::Print, State::Ground),
			// UTF-8 2 byte code point
			0xC0...0xDF => (Action::Print, State::Ground),
			// UTF-8 3 byte code point
			0xE0...0xEF => (Action::Print, State::Ground),
			// UTF-8 4 byte code point
			0xF0...0xF7 => (Action::Print, State::Ground),

			_ => (Action::None, State::Error),
		},
		State::OscString => match byte {
			0x00...0x17 => (Action::Ignore, State::OscString),
			0x19 => (Action::Ignore, State::OscString),
			0x1C...0x1F => (Action::Ignore, State::OscString),
			0x20...0x7F => (Action::OscPut, State::OscString),

			_ => (Action::None, State::Error),
		},
		State::SosPmApcString => match byte {
			0x00...0x17 => (Action::Ignore, State::SosPmApcString),
			0x19 => (Action::Ignore, State::SosPmApcString),
			0x1C...0x1F => (Action::Ignore, State::SosPmApcString),
			0x20...0x7F => (Action::Ignore, State::SosPmApcString),

			_ => (Action::None, State::Error),
		},
	}
}

use lazy_static::lazy_static;
lazy_static! {
	pub static ref STATE_TABLE: [[u8; 256]; 16] = build_state_table();
}

fn build_state_table() -> [[u8; 256]; 16] {
	let mut state_table = [[0; 256]; 16];
	for &from_state in STATES {
		for byte in 0..0xFF {
			let (to_action, to_state) = state_table_fn(from_state, byte);
			let value = ((to_action as u8) << 4) | (to_state as u8);
			state_table[from_state as usize][byte as usize] = value;
		}
	}
	return state_table;
}

pub fn query(from_state: State, byte: u8) -> (Action, State) {
	let table_value = STATE_TABLE[from_state as usize][byte as usize];
	let to_action: Action = unsafe { std::mem::transmute((table_value & 0xF0) >> 4) };
	let to_state: State = unsafe { std::mem::transmute(table_value & 0x0F) };
	(to_action, to_state)
}