diff options
author | Kirill Chibisov <contact@kchibisov.com> | 2025-01-12 22:54:18 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-12 22:54:18 +0300 |
commit | b3fba5651a264d164d840bfd4f8f0a30fa0b555b (patch) | |
tree | 7063e6cd422f4f23b41a98a1e9ec29a4f5d10016 | |
parent | c18ef2206af630f729835da237381628650005aa (diff) | |
download | r-alacritty-vte-b3fba5651a264d164d840bfd4f8f0a30fa0b555b.tar.gz r-alacritty-vte-b3fba5651a264d164d840bfd4f8f0a30fa0b555b.tar.bz2 r-alacritty-vte-b3fba5651a264d164d840bfd4f8f0a30fa0b555b.zip |
Rewrite table based state change to `match` based
The table based state change was too complex to make guesses why it's
getting slow and too fragile, as in modifying the amount of
states/actions were slowing down, even though, they were not used.
Rewrite the state + action change exactly how it's in [1] with respect
to our modifications/C1, etc. The new implementation is generally faster
than the previous one and is easier for compiler to reason about and
generate more efficient structures.
Also, the structure got way simpler to follow, since it matches the
spec pretty much exactly.
[1] - https://vt100.net/emu/dec_ansi_parser
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | src/definitions.rs | 99 | ||||
-rw-r--r-- | src/lib.rs | 540 | ||||
-rw-r--r-- | src/table.rs | 188 | ||||
-rw-r--r-- | vte_generate_state_changes/Cargo.toml | 15 | ||||
l--------- | vte_generate_state_changes/LICENSE-APACHE | 1 | ||||
l--------- | vte_generate_state_changes/LICENSE-MIT | 1 | ||||
-rw-r--r-- | vte_generate_state_changes/src/lib.rs | 176 |
8 files changed, 392 insertions, 632 deletions
@@ -13,9 +13,6 @@ name = "vte" edition = "2021" rust-version = "1.62.1" -[workspace] -members = ["vte_generate_state_changes"] - [features] ansi = ["log", "cursor-icon", "bitflags"] default = ["no_std"] @@ -29,4 +26,3 @@ cursor-icon = { version = "1.0.0", default-features = false, optional = true } log = { version = "0.4.17", optional = true } memchr = "2.7.4" serde = { version = "1.0.160", features = ["derive"], optional = true } -vte_generate_state_changes = { version = "0.2.0", path = "vte_generate_state_changes" } diff --git a/src/definitions.rs b/src/definitions.rs deleted file mode 100644 index 694c783..0000000 --- a/src/definitions.rs +++ /dev/null @@ -1,99 +0,0 @@ -use core::mem; - -#[allow(dead_code)] -#[repr(u8)] -#[derive(PartialEq, Eq, Debug, Default, Copy, Clone)] -pub enum State { - CsiEntry, - CsiIgnore, - CsiIntermediate, - CsiParam, - DcsEntry, - DcsIgnore, - DcsIntermediate, - DcsParam, - DcsPassthrough, - Escape, - EscapeIntermediate, - OscString, - SosPmApcString, - Anywhere, - #[default] - Ground, -} - -// NOTE: Removing the unused actions prefixed with `_` will reduce performance. -#[allow(dead_code)] -#[repr(u8)] -#[derive(PartialEq, Eq, Debug, Clone, Copy)] -pub enum Action { - None, - _Clear, - Collect, - CsiDispatch, - EscDispatch, - Execute, - _Hook, - _Ignore, - _OscEnd, - OscPut, - _OscStart, - Param, - _Print, - Put, - _Unhook, -} - -/// Unpack a u8 into a State and Action -/// -/// The implementation of this assumes that there are *precisely* 16 variants -/// for both Action and State. Furthermore, it assumes that the enums are -/// tag-only; that is, there is no data in any variant. -/// -/// Bad things will happen if those invariants are violated. -#[inline(always)] -pub fn unpack(delta: u8) -> (State, Action) { - unsafe { - ( - // State is stored in bottom 4 bits - mem::transmute::<u8, State>(delta & 0x0F), - // Action is stored in top 4 bits - mem::transmute::<u8, Action>(delta >> 4), - ) - } -} - -#[inline(always)] -pub const fn pack(state: State, action: Action) -> u8 { - (action as u8) << 4 | state as u8 -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn unpack_state_action() { - match unpack(0xEE) { - (State::Ground, Action::_Unhook) => (), - _ => panic!("unpack failed"), - } - - match unpack(0x0E) { - (State::Ground, Action::None) => (), - _ => panic!("unpack failed"), - } - - match unpack(0xE0) { - (State::CsiEntry, Action::_Unhook) => (), - _ => panic!("unpack failed"), - } - } - - #[test] - fn pack_state_action() { - assert_eq!(pack(State::Ground, Action::_Unhook), 0xEE); - assert_eq!(pack(State::Ground, Action::None), 0x0E); - assert_eq!(pack(State::CsiEntry, Action::_Unhook), 0xE0); - } -} @@ -35,13 +35,10 @@ use core::str; #[cfg(feature = "no_std")] use arrayvec::ArrayVec; -mod definitions; mod params; -mod table; #[cfg(feature = "ansi")] pub mod ansi; -use definitions::{unpack, Action, State}; pub use params::{Params, ParamsIter}; const MAX_INTERMEDIATES: usize = 2; @@ -113,7 +110,7 @@ impl<const OSC_RAW_BUF_SIZE: usize> Parser<OSC_RAW_BUF_SIZE> { let mut i = 0; // Handle partial codepoints from previous calls to `advance`. - if self.partial_utf8_len > 0 { + if self.partial_utf8_len != 0 { i += self.advance_partial_utf8(performer, bytes); } @@ -121,12 +118,9 @@ impl<const OSC_RAW_BUF_SIZE: usize> Parser<OSC_RAW_BUF_SIZE> { match self.state { State::Ground => i += self.advance_ground(performer, &bytes[i..]), _ => { + // Inlining it results in worse codegen. let byte = bytes[i]; - let change = table::STATE_CHANGES[self.state as usize][byte as usize]; - let (state, action) = unpack(change); - - self.perform_state_change(performer, state, action, byte); - + self.change_state(performer, byte); i += 1; }, } @@ -159,12 +153,9 @@ impl<const OSC_RAW_BUF_SIZE: usize> Parser<OSC_RAW_BUF_SIZE> { match self.state { State::Ground => i += self.advance_ground(performer, &bytes[i..]), _ => { + // Inlining it results in worse codegen. let byte = bytes[i]; - let change = table::STATE_CHANGES[self.state as usize][byte as usize]; - let (state, action) = unpack(change); - - self.perform_state_change(performer, state, action, byte); - + self.change_state(performer, byte); i += 1; }, } @@ -173,164 +164,398 @@ impl<const OSC_RAW_BUF_SIZE: usize> Parser<OSC_RAW_BUF_SIZE> { i } - #[inline] - fn perform_state_change<P>(&mut self, performer: &mut P, state: State, action: Action, byte: u8) - where - P: Perform, - { - if state == State::Anywhere { - self.perform_action(performer, action, byte); - return; + #[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::SosPmApcString => self.anywhere(performer, byte), + State::Ground => unreachable!(), } + } - match self.state { - State::DcsPassthrough => performer.unhook(), - State::OscString => { - let param_idx = self.osc_num_params; - let idx = self.osc_raw.len(); - - match param_idx { - // Finish last parameter if not already maxed - MAX_OSC_PARAMS => (), - - // First param is special - 0 to current byte index - 0 => { - self.osc_params[param_idx] = (0, idx); - self.osc_num_params += 1; - }, + #[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), + } + } - // All other params depend on previous indexing - _ => { - let prev = self.osc_params[param_idx - 1]; - let begin = prev.1; - self.osc_params[param_idx] = (begin, idx); - self.osc_num_params += 1; - }, - } - self.osc_dispatch(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), } + } - if action == Action::None { - match state { - State::CsiEntry | State::DcsEntry | State::Escape => self.reset_params(), - State::DcsPassthrough => { - if self.params.is_full() { - self.ignoring = true; - } else { - self.params.push(self.param); - } + #[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), + } + } - performer.hook( - self.params(), - self.intermediates(), - self.ignoring, - byte as char, - ); - }, - State::OscString => { - self.osc_raw.clear(); - self.osc_num_params = 0; - }, - _ => (), - } - } else { - self.perform_action(performer, action, 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), } + } - self.state = state; + #[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] - fn perform_action<P: Perform>(&mut self, performer: &mut P, action: Action, byte: u8) { - match action { - Action::Execute => performer.execute(byte), - Action::Put => performer.put(byte), - Action::OscPut => { + #[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 => self.state = State::SosPmApcString, + 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.osc_raw.clear(); + self.osc_num_params = 0; + self.state = State::OscString + }, + 0x5E..=0x5F => self.state = State::SosPmApcString, + 0x60..=0x7E => { + performer.esc_dispatch(self.intermediates(), self.ignoring, byte); + self.state = State::Ground + }, + // Anywhere. + 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<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 => { #[cfg(feature = "no_std")] { if self.osc_raw.is_full() { return; } } - - let idx = self.osc_raw.len(); - - // Param separator - if byte == b';' { - let param_idx = self.osc_num_params; - match param_idx { - // Only process up to MAX_OSC_PARAMS - MAX_OSC_PARAMS => return, - - // First param is special - 0 to current byte index - 0 => { - self.osc_params[param_idx] = (0, idx); - }, - - // All other params depend on previous indexing - _ => { - let prev = self.osc_params[param_idx - 1]; - let begin = prev.1; - self.osc_params[param_idx] = (begin, idx); - }, - } - - self.osc_num_params += 1; - } else { - self.osc_raw.push(byte); - } + self.action_osc_put_param() }, - Action::CsiDispatch => { - if self.params.is_full() { - self.ignoring = true; - } else { - self.params.push(self.param); - } + _ => self.action_osc_put(byte), + } + } - performer.csi_dispatch( - self.params(), - self.intermediates(), - self.ignoring, - byte as char, - ); - }, - Action::EscDispatch => { - performer.esc_dispatch(self.intermediates(), self.ignoring, 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 }, - Action::Collect => { - if self.intermediate_idx == MAX_INTERMEDIATES { - self.ignoring = true; - } else { - self.intermediates[self.intermediate_idx] = byte; - self.intermediate_idx += 1; - } + 0x1B => { + self.reset_params(); + self.state = State::Escape }, - Action::Param => { - if self.params.is_full() { - self.ignoring = true; - return; - } + _ => (), + } + } - match byte { - b';' => { - self.params.push(self.param); - self.param = 0; - }, - b':' => { - self.params.extend(self.param); - self.param = 0; - }, - _ => { - // Continue collecting bytes into param - self.param = self.param.saturating_mul(10); - self.param = self.param.saturating_add((byte - b'0') as u16); - }, - } + #[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; + } + } + + /// Advance to the next subparameter. + #[inline] + fn action_subparam(&mut self) { + if self.params.is_full() { + self.ignoring = true; + } else { + self.params.extend(self.param); + self.param = 0; + } + } + + /// Advance to the next parameter. + #[inline] + fn action_param(&mut self) { + if self.params.is_full() { + self.ignoring = true; + } else { + self.params.push(self.param); + self.param = 0; + } + } + + /// Advance inside the parameter without terminating it. + #[inline] + fn action_paramnext(&mut self, byte: u8) { + if self.params.is_full() { + self.ignoring = true; + } else { + // Continue collecting bytes into param. + self.param = self.param.saturating_mul(10); + self.param = self.param.saturating_add((byte - b'0') as u16); + } + } + + /// Add OSC param separator. + #[inline] + fn action_osc_put_param(&mut self) { + let idx = self.osc_raw.len(); + + let param_idx = self.osc_num_params; + match param_idx { + // First param is special - 0 to current byte index. + 0 => self.osc_params[param_idx] = (0, idx), + + // Only process up to MAX_OSC_PARAMS. + MAX_OSC_PARAMS => return, + + // All other params depend on previous indexing. + _ => { + 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) { + #[cfg(feature = "no_std")] + { + if self.osc_raw.is_full() { + return; + } + } + 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; } /// Reset escape sequence parameters and intermediates. @@ -504,6 +729,25 @@ impl<const OSC_RAW_BUF_SIZE: usize> Parser<OSC_RAW_BUF_SIZE> { } } +#[derive(PartialEq, Eq, Debug, Default, Copy, Clone)] +enum State { + CsiEntry, + CsiIgnore, + CsiIntermediate, + CsiParam, + DcsEntry, + DcsIgnore, + DcsIntermediate, + DcsParam, + DcsPassthrough, + Escape, + EscapeIntermediate, + OscString, + SosPmApcString, + #[default] + Ground, +} + /// Performs actions requested by the Parser /// /// Actions in this case mean, for example, handling a CSI escape sequence diff --git a/src/table.rs b/src/table.rs deleted file mode 100644 index ac288e7..0000000 --- a/src/table.rs +++ /dev/null @@ -1,188 +0,0 @@ -use vte_generate_state_changes::generate_state_changes; - -/// This is the state change table. It's indexed first by current state and then -/// by the next character in the pty stream. -use crate::definitions::{pack, Action, State}; - -// Generate state changes at compile-time -pub const STATE_CHANGES: [[u8; 256]; 13] = state_changes(); -generate_state_changes!(state_changes, { - Escape { - 0x00..=0x17 => (Anywhere, Execute), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, Execute), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, Execute), - 0x7f => (Anywhere, None), - 0x20..=0x2f => (EscapeIntermediate, Collect), - 0x30..=0x4f => (Ground, EscDispatch), - 0x51..=0x57 => (Ground, EscDispatch), - 0x59 => (Ground, EscDispatch), - 0x5a => (Ground, EscDispatch), - 0x5c => (Ground, EscDispatch), - 0x60..=0x7e => (Ground, EscDispatch), - 0x5b => (CsiEntry, None), - 0x5d => (OscString, None), - 0x50 => (DcsEntry, None), - 0x58 => (SosPmApcString, None), - 0x5e => (SosPmApcString, None), - 0x5f => (SosPmApcString, None), - }, - - EscapeIntermediate { - 0x00..=0x17 => (Anywhere, Execute), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, Execute), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, Execute), - 0x20..=0x2f => (Anywhere, Collect), - 0x7f => (Anywhere, None), - 0x30..=0x7e => (Ground, EscDispatch), - }, - - CsiEntry { - 0x00..=0x17 => (Anywhere, Execute), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, Execute), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, Execute), - 0x7f => (Anywhere, None), - 0x20..=0x2f => (CsiIntermediate, Collect), - 0x30..=0x39 => (CsiParam, Param), - 0x3a..=0x3b => (CsiParam, Param), - 0x3c..=0x3f => (CsiParam, Collect), - 0x40..=0x7e => (Ground, CsiDispatch), - }, - - CsiIgnore { - 0x00..=0x17 => (Anywhere, Execute), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, Execute), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, Execute), - 0x20..=0x3f => (Anywhere, None), - 0x7f => (Anywhere, None), - 0x40..=0x7e => (Ground, None), - }, - - CsiParam { - 0x00..=0x17 => (Anywhere, Execute), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, Execute), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, Execute), - 0x30..=0x39 => (Anywhere, Param), - 0x3a..=0x3b => (Anywhere, Param), - 0x7f => (Anywhere, None), - 0x3c..=0x3f => (CsiIgnore, None), - 0x20..=0x2f => (CsiIntermediate, Collect), - 0x40..=0x7e => (Ground, CsiDispatch), - }, - - CsiIntermediate { - 0x00..=0x17 => (Anywhere, Execute), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, Execute), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, Execute), - 0x20..=0x2f => (Anywhere, Collect), - 0x7f => (Anywhere, None), - 0x30..=0x3f => (CsiIgnore, None), - 0x40..=0x7e => (Ground, CsiDispatch), - }, - - DcsEntry { - 0x00..=0x17 => (Anywhere, None), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, None), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, None), - 0x7f => (Anywhere, None), - 0x20..=0x2f => (DcsIntermediate, Collect), - 0x30..=0x39 => (DcsParam, Param), - 0x3a..=0x3b => (DcsParam, Param), - 0x3c..=0x3f => (DcsParam, Collect), - 0x40..=0x7e => (DcsPassthrough, None), - }, - - DcsIntermediate { - 0x00..=0x17 => (Anywhere, None), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, None), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, None), - 0x20..=0x2f => (Anywhere, Collect), - 0x7f => (Anywhere, None), - 0x30..=0x3f => (DcsIgnore, None), - 0x40..=0x7e => (DcsPassthrough, None), - }, - - DcsIgnore { - 0x00..=0x17 => (Anywhere, None), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, None), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, None), - 0x20..=0x7f => (Anywhere, None), - 0x9c => (Ground, None), - }, - - DcsParam { - 0x00..=0x17 => (Anywhere, None), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, None), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, None), - 0x30..=0x39 => (Anywhere, Param), - 0x3a..=0x3b => (Anywhere, Param), - 0x7f => (Anywhere, None), - 0x3c..=0x3f => (DcsIgnore, None), - 0x20..=0x2f => (DcsIntermediate, Collect), - 0x40..=0x7e => (DcsPassthrough, None), - }, - - DcsPassthrough { - 0x00..=0x17 => (Anywhere, Put), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, Put), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, Put), - 0x20..=0x7e => (Anywhere, Put), - 0x7f => (Anywhere, None), - 0x9c => (Ground, None), - }, - - SosPmApcString { - 0x00..=0x17 => (Anywhere, None), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, None), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, None), - 0x20..=0x7f => (Anywhere, None), - 0x9c => (Ground, None), - }, - - OscString { - 0x00..=0x06 => (Anywhere, None), - 0x07 => (Ground, None), - 0x08..=0x17 => (Anywhere, None), - 0x18 => (Ground, Execute), - 0x19 => (Anywhere, None), - 0x1a => (Ground, Execute), - 0x1b => (Escape, None), - 0x1c..=0x1f => (Anywhere, None), - 0x20..=0xff => (Anywhere, OscPut), - } -}); diff --git a/vte_generate_state_changes/Cargo.toml b/vte_generate_state_changes/Cargo.toml deleted file mode 100644 index dd0e714..0000000 --- a/vte_generate_state_changes/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -authors = ["Christian Duerr <contact@christianduerr.com>"] -description = "Proc macro for generating VTE state changes" -repository = "https://github.com/alacritty/vte" -name = "vte_generate_state_changes" -license = "Apache-2.0 OR MIT" -version = "0.2.0" -edition = "2018" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0.6" -quote = "1.0.2" diff --git a/vte_generate_state_changes/LICENSE-APACHE b/vte_generate_state_changes/LICENSE-APACHE deleted file mode 120000 index 965b606..0000000 --- a/vte_generate_state_changes/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE
\ No newline at end of file diff --git a/vte_generate_state_changes/LICENSE-MIT b/vte_generate_state_changes/LICENSE-MIT deleted file mode 120000 index 76219eb..0000000 --- a/vte_generate_state_changes/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT
\ No newline at end of file diff --git a/vte_generate_state_changes/src/lib.rs b/vte_generate_state_changes/src/lib.rs deleted file mode 100644 index ff8ea49..0000000 --- a/vte_generate_state_changes/src/lib.rs +++ /dev/null @@ -1,176 +0,0 @@ -#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)] - -extern crate proc_macro; - -use std::iter::Peekable; - -use proc_macro2::TokenTree::{Group, Literal, Punct}; -use proc_macro2::{token_stream, TokenStream, TokenTree}; -use quote::quote; - -/// Create a `const fn` which will return an array with all state changes. -#[proc_macro] -pub fn generate_state_changes(item: proc_macro::TokenStream) -> proc_macro::TokenStream { - // Convert from proc_macro -> proc_macro2 - let item: TokenStream = item.into(); - let mut iter = item.into_iter().peekable(); - - // Determine output function name - let fn_name = iter.next().unwrap(); - - // Separator between name and body with state changes - expect_punct(&mut iter, ','); - - // Create token stream to assign each state change to the array - let assignments_stream = states_stream(&mut iter); - - quote!( - const fn #fn_name() -> [[u8; 256]; 13] { - let mut state_changes = [[0; 256]; 13]; - - #assignments_stream - - state_changes - } - ) - .into() -} - -/// Generate the array assignment statements for all origin states. -fn states_stream(iter: &mut impl Iterator<Item = TokenTree>) -> TokenStream { - let mut states_stream = next_group(iter).into_iter().peekable(); - - // Loop over all origin state entries - let mut tokens = quote!(); - while states_stream.peek().is_some() { - // Add all mappings for this state - tokens.extend(state_entry_stream(&mut states_stream)); - - // Allow trailing comma - optional_punct(&mut states_stream, ','); - } - tokens -} - -/// Generate the array assignment statements for one origin state. -fn state_entry_stream(iter: &mut Peekable<token_stream::IntoIter>) -> TokenStream { - // Origin state name - let state = iter.next().unwrap(); - - // Token stream with all the byte->target mappings - let mut changes_stream = next_group(iter).into_iter().peekable(); - - let mut tokens = quote!(); - while changes_stream.peek().is_some() { - // Add next mapping for this state - tokens.extend(change_stream(&mut changes_stream, &state)); - - // Allow trailing comma - optional_punct(&mut changes_stream, ','); - } - tokens -} - -/// Generate the array assignment statement for a single byte->target mapping -/// for one state. -fn change_stream(iter: &mut Peekable<token_stream::IntoIter>, state: &TokenTree) -> TokenStream { - // Start of input byte range - let start = next_usize(iter); - - // End of input byte range - let end = if optional_punct(iter, '.') { - // Read inclusive end of range - expect_punct(iter, '.'); - expect_punct(iter, '='); - next_usize(iter) - } else { - // Without range, end is equal to start - start - }; - - // Separator between byte input range and output state - expect_punct(iter, '='); - expect_punct(iter, '>'); - - // Token stream with target state and action - let mut target_change_stream = next_group(iter).into_iter().peekable(); - - let mut tokens = quote!(); - while target_change_stream.peek().is_some() { - // Target state/action for all bytes in the range - let (target_state, target_action) = target_change(&mut target_change_stream); - - // Create a new entry for every byte in the range - for byte in start..=end { - tokens.extend(quote!( - state_changes[State::#state as usize][#byte] = - pack(State::#target_state, Action::#target_action); - )); - } - } - tokens -} - -/// Get next target state and action. -fn target_change(iter: &mut Peekable<token_stream::IntoIter>) -> (TokenTree, TokenTree) { - let target_state = iter.next().unwrap(); - - // Separator between state and action - expect_punct(iter, ','); - - let target_action = iter.next().unwrap(); - - (target_state, target_action) -} - -/// Check if next token matches specific punctuation. -fn optional_punct(iter: &mut Peekable<token_stream::IntoIter>, c: char) -> bool { - match iter.peek() { - Some(Punct(punct)) if punct.as_char() == c => iter.next().is_some(), - _ => false, - } -} - -/// Ensure next token matches specific punctuation. -/// -/// # Panics -/// -/// Panics if the punctuation does not match. -fn expect_punct(iter: &mut impl Iterator<Item = TokenTree>, c: char) { - match iter.next() { - Some(Punct(ref punct)) if punct.as_char() == c => (), - token => panic!("Expected punctuation '{}', but got {:?}", c, token), - } -} - -/// Get next token as [`usize`]. -/// -/// # Panics -/// -/// Panics if the next token is not a [`usize`] in hex or decimal literal -/// format. -fn next_usize(iter: &mut impl Iterator<Item = TokenTree>) -> usize { - match iter.next() { - Some(Literal(literal)) => { - let literal = literal.to_string(); - if let Some(prefix) = literal.strip_prefix("0x") { - usize::from_str_radix(prefix, 16).unwrap() - } else { - literal.parse::<usize>().unwrap() - } - }, - token => panic!("Expected literal, but got {:?}", token), - } -} - -/// Get next token as [`Group`]. -/// -/// # Panics -/// -/// Panics if the next token is not a [`Group`]. -fn next_group(iter: &mut impl Iterator<Item = TokenTree>) -> TokenStream { - match iter.next() { - Some(Group(group)) => group.stream(), - token => panic!("Expected group, but got {:?}", token), - } -} |