// DEC VT500 escape-sequence state machine: parser states, action bit flags,
// and the `transition` lookup.
//
// Reference: Paul Flo Williams' VT500 parser state diagram
// https://vt100.net/emu/dec_ansi_parser — the "State Definitions" and
// "Action Definitions" sections map 1:1 to `ParseState` and the `Action`
// bit flags. The grammar is ECMA-48 §5.4/§8.3.
///|
/// VT500 parser states (mutually exclusive).
pub enum ParseState {
Ground
Escape
EscapeIntermediate
CsiEntry
CsiParam
CsiIntermediate
CsiIgnore
OscString
DcsEntry
DcsParam
DcsIntermediate
DcsPassthrough
DcsIgnore
SosPmApcString
} derive(Eq, Debug)
///|
/// Action bit flags (a byte can trigger several).
pub const ACTION_NONE : Int = 0
///|
pub const ACTION_PRINT : Int = 1 << 0
///|
pub const ACTION_EXECUTE : Int = 1 << 1
///|
pub const ACTION_CSI_DISPATCH : Int = 1 << 2
///|
pub const ACTION_ESC_DISPATCH : Int = 1 << 3
///|
pub const ACTION_HOOK : Int = 1 << 4
///|
pub const ACTION_PUT : Int = 1 << 5
///|
pub const ACTION_UNHOOK : Int = 1 << 6
///|
pub const ACTION_OSC_START : Int = 1 << 7
///|
pub const ACTION_OSC_PUT : Int = 1 << 8
///|
pub const ACTION_OSC_END : Int = 1 << 9
///|
/// Test whether `action` contains `flag`.
pub fn action_has(action : Int, flag : Int) -> Bool {
(action & flag) != 0
}
///| DEC "anywhere" transition for a raw 8-bit C1 control byte (0x80..=0x9f).
///| The introducers start their sequence; ST is a no-op; the rest execute and
///|
/// return to ground.
pub fn c1_transition(b : Int) -> (Int, ParseState) {
match b {
0x90 => (ACTION_NONE, ParseState::DcsEntry) // DCS
0x98 | 0x9e | 0x9f => (ACTION_NONE, ParseState::SosPmApcString) // SOS/PM/APC
0x9b => (ACTION_NONE, ParseState::CsiEntry) // CSI
0x9d => (ACTION_OSC_START, ParseState::OscString) // OSC
0x9c => (ACTION_NONE, ParseState::Ground) // ST
_ => (ACTION_EXECUTE, ParseState::Ground) // other C1
}
}
///| C1 byte arriving inside a non-ground state: abort the sequence and either
///|
/// terminate a string (ST 0x9c) or execute/re-enter the introducer.
pub fn c1_in_sequence(state : ParseState, b : Int) -> (Int, ParseState) {
match (state, b) {
(ParseState::OscString, 0x9c) => (ACTION_OSC_END, ParseState::Ground)
(ParseState::DcsPassthrough, 0x9c) => (ACTION_UNHOOK, ParseState::Ground)
(ParseState::SosPmApcString, 0x9c) => (ACTION_NONE, ParseState::Ground)
_ => c1_transition(b)
}
}
///| The DEC VT500 transition table for the non-ground states. A byte can
///|
/// trigger several actions; the result is `(action_bitmask, next_state)`.
pub fn transition(state : ParseState, b : Int) -> (Int, ParseState) {
match state {
ParseState::Escape =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f => (ACTION_EXECUTE, ParseState::Escape)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
0x20..=0x2f => (ACTION_NONE, ParseState::EscapeIntermediate)
0x30..=0x4f | 0x51..=0x57 | 0x59 | 0x5a | 0x5c | 0x60..=0x7e =>
(ACTION_ESC_DISPATCH, ParseState::Ground)
0x50 => (ACTION_NONE, ParseState::DcsEntry) // 'P' → DCS
0x58 | 0x5e | 0x5f => (ACTION_NONE, ParseState::SosPmApcString) // 'X' '^' '_'
0x5b => (ACTION_NONE, ParseState::CsiEntry) // '[' → CSI
0x5d => (ACTION_OSC_START, ParseState::OscString) // ']' → OSC
_ => (ACTION_NONE, ParseState::Escape)
}
ParseState::EscapeIntermediate =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f =>
(ACTION_EXECUTE, ParseState::EscapeIntermediate)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
0x20..=0x2f => (ACTION_NONE, ParseState::EscapeIntermediate)
0x30..=0x7e => (ACTION_ESC_DISPATCH, ParseState::Ground)
_ => (ACTION_NONE, ParseState::EscapeIntermediate)
}
ParseState::CsiEntry =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f =>
(ACTION_EXECUTE, ParseState::CsiEntry)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
0x20..=0x2f => (ACTION_NONE, ParseState::CsiIntermediate)
0x30..=0x39 | 0x3a | 0x3b | 0x3c..=0x3f =>
(ACTION_NONE, ParseState::CsiParam)
0x40..=0x7e => (ACTION_CSI_DISPATCH, ParseState::Ground)
_ => (ACTION_NONE, ParseState::CsiEntry)
}
ParseState::CsiParam =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f =>
(ACTION_EXECUTE, ParseState::CsiParam)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
0x20..=0x2f => (ACTION_NONE, ParseState::CsiIntermediate)
0x30..=0x39 | 0x3a | 0x3b => (ACTION_NONE, ParseState::CsiParam)
0x3c..=0x3f => (ACTION_NONE, ParseState::CsiIgnore)
0x40..=0x7e => (ACTION_CSI_DISPATCH, ParseState::Ground)
_ => (ACTION_NONE, ParseState::CsiParam)
}
ParseState::CsiIntermediate =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f =>
(ACTION_EXECUTE, ParseState::CsiIntermediate)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
0x20..=0x2f => (ACTION_NONE, ParseState::CsiIntermediate)
0x30..=0x3f => (ACTION_NONE, ParseState::CsiIgnore)
0x40..=0x7e => (ACTION_CSI_DISPATCH, ParseState::Ground)
_ => (ACTION_NONE, ParseState::CsiIntermediate)
}
ParseState::CsiIgnore =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f =>
(ACTION_EXECUTE, ParseState::CsiIgnore)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
0x20..=0x3f => (ACTION_NONE, ParseState::CsiIgnore)
0x40..=0x7e => (ACTION_NONE, ParseState::Ground)
_ => (ACTION_NONE, ParseState::CsiIgnore)
}
ParseState::OscString =>
match b {
0x00..=0x06 | 0x08..=0x17 | 0x19 | 0x1c..=0x1f =>
(ACTION_NONE, ParseState::OscString)
0x07 => (ACTION_OSC_END, ParseState::Ground)
0x18 | 0x1a => (ACTION_OSC_END | ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_OSC_END, ParseState::Escape)
0x9c => (ACTION_OSC_END, ParseState::Ground)
_ => (ACTION_OSC_PUT, ParseState::OscString)
}
ParseState::DcsEntry =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f => (ACTION_NONE, ParseState::DcsEntry)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
0x20..=0x2f => (ACTION_NONE, ParseState::DcsIntermediate)
0x30..=0x39 | 0x3a | 0x3b | 0x3c..=0x3f =>
(ACTION_NONE, ParseState::DcsParam)
0x40..=0x7e => (ACTION_HOOK, ParseState::DcsPassthrough)
_ => (ACTION_NONE, ParseState::DcsEntry)
}
ParseState::DcsParam =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f => (ACTION_NONE, ParseState::DcsParam)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
0x20..=0x2f => (ACTION_NONE, ParseState::DcsIntermediate)
0x30..=0x39 | 0x3a | 0x3b => (ACTION_NONE, ParseState::DcsParam)
0x3c..=0x3f => (ACTION_NONE, ParseState::DcsIgnore)
0x40..=0x7e => (ACTION_HOOK, ParseState::DcsPassthrough)
_ => (ACTION_NONE, ParseState::DcsParam)
}
ParseState::DcsIntermediate =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f =>
(ACTION_NONE, ParseState::DcsIntermediate)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
0x20..=0x2f => (ACTION_NONE, ParseState::DcsIntermediate)
0x30..=0x3f => (ACTION_NONE, ParseState::DcsIgnore)
0x40..=0x7e => (ACTION_HOOK, ParseState::DcsPassthrough)
_ => (ACTION_NONE, ParseState::DcsIntermediate)
}
ParseState::DcsPassthrough =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x7e =>
(ACTION_PUT, ParseState::DcsPassthrough)
0x18 | 0x1a => (ACTION_UNHOOK | ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_UNHOOK, ParseState::Escape)
0x9c => (ACTION_UNHOOK, ParseState::Ground)
_ => (ACTION_NONE, ParseState::DcsPassthrough)
}
ParseState::DcsIgnore =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f => (ACTION_NONE, ParseState::DcsIgnore)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
0x20..=0x7f => (ACTION_NONE, ParseState::DcsIgnore)
0x9c => (ACTION_NONE, ParseState::Ground)
_ => (ACTION_NONE, ParseState::DcsIgnore)
}
ParseState::SosPmApcString =>
match b {
0x00..=0x17 | 0x19 | 0x1c..=0x1f =>
(ACTION_NONE, ParseState::SosPmApcString)
0x18 | 0x1a => (ACTION_EXECUTE, ParseState::Ground)
0x1b => (ACTION_NONE, ParseState::Escape)
// The first content byte is the "action" (e.g. `G` for kitty graphics
// APC `_G`); dispatch it as a hook and reuse the DCS passthrough state.
0x20..=0x7e => (ACTION_HOOK, ParseState::DcsPassthrough)
0x9c => (ACTION_NONE, ParseState::Ground)
_ => (ACTION_NONE, ParseState::SosPmApcString)
}
ParseState::Ground => (ACTION_NONE, ParseState::Ground)
}
}