diff options
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | examples/parselog.rs | 59 | ||||
-rw-r--r-- | src/definitions.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 189 |
4 files changed, 182 insertions, 70 deletions
@@ -1,5 +1,5 @@ [package] -name = "vtparse" +name = "vte" version = "0.1.0" authors = ["Joe Wilm <joe@jwilm.com>"] diff --git a/examples/parselog.rs b/examples/parselog.rs index f4ae86a..5c1836b 100644 --- a/examples/parselog.rs +++ b/examples/parselog.rs @@ -3,51 +3,60 @@ extern crate vtparse; use std::io::{self, Read}; -use vtparse::{StateMachine, Parser}; - -/// A type implementing Parser that just logs actions +/// A type implementing Perform that just logs actions struct Log; -impl Parser for Log { +impl vte::Perform for Log { fn print(&mut self, c: char) { println!("[print] {:?}", c); } - fn execute(&mut self, _machine: &StateMachine, byte: u8) { - println!("[execute] byte={:02x}", byte); + + fn execute(&mut self, byte: u8) { + println!("[execute] {:02x}", byte); } - fn hook(&mut self, _machine: &StateMachine, byte: u8) { - println!("[hook] byte={:02x}", byte); + + fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, byte: u8) { + println!("[hook] params={:?}, intermediates={:?}, ignore={:?}, byte={:02x}", + params, intermediates, ignore, byte); } - fn put(&mut self, _machine: &StateMachine, byte: u8) { - println!("[put] byte={:02x}", byte); + + fn put(&mut self, byte: u8) { + println!("[put] {:02x}", byte); } - fn osc_start(&mut self, _machine: &StateMachine, byte: u8) { - println!("[osc_start] byte={:02x}", byte); + + fn unhook(&mut self, byte: u8) { + println!("[unhook] {:02x}", byte); } - fn osc_put(&mut self, _machine: &StateMachine, byte: u8) { - println!("[osc_put] byte={:02x}", byte); + + fn osc_start(&mut self) { + println!("[osc_start]"); } - fn osc_end(&mut self, _machine: &StateMachine, byte: u8) { - println!("[osc_end] byte={:02x}", byte); + + fn osc_put(&mut self, byte: u8) { + println!("[osc_put] {:02x}", byte); } - fn unhook(&mut self, _machine: &StateMachine, byte: u8) { - println!("[unhook] byte={:02x}", byte); + + fn osc_end(&mut self, byte: u8) { + println!("[osc_end] {:02x}", byte); } - fn csi_dispatch(&mut self, machine: &StateMachine, c: char) { - println!("[csi_dispatch] params={:?}, intermediates={:?}, action={:?}", - machine.params(), machine.intermediates(), c); + + fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { + println!("[csi_dispatch] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", + params, intermediates, ignore, c); } - fn esc_dispatch(&mut self, machine: &StateMachine, byte: u8) { - println!("[csi_dispatch] params={:?}, intermediates={:?}, action={:?}", - machine.params(), machine.intermediates(), byte as char); + + fn esc_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, byte: u8) { + println!("[esc_dispatch] params={:?}, intermediates={:?}, ignore={:?}, byte={:02x}", + params, intermediates, ignore, byte); } + } fn main() { let input = io::stdin(); let mut handle = input.lock(); - let mut statemachine = StateMachine::new(); + let mut statemachine = vte::Parser::new(); let mut parser = Log; let mut buf: [u8; 2048] = unsafe { std::mem::uninitialized() }; diff --git a/src/definitions.rs b/src/definitions.rs index ded49cf..d8faf53 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -1,3 +1,4 @@ +#[allow(dead_code)] #[derive(Debug, Copy, Clone)] pub enum State { Anywhere = 0, @@ -18,6 +19,7 @@ pub enum State { Utf8 = 15, } +#[allow(dead_code)] #[derive(Debug, Clone, Copy)] pub enum Action { None = 0, @@ -1,8 +1,33 @@ +//! Parser for implementing virtual terminal emulators +//! +//! [`Parser`] is implemented according to [Paul Williams' ANSI parser +//! state machine]. The state machine doesn't assign meaning to the parsed data +//! and is thus not itself sufficient for writing a terminal emulator. Instead, +//! it is expected that an implementation of [`Perform`] is provided which does +//! something useful with the parsed data. The [`Parser`] handles the book +//! keeping, and the [`Perform`] gets to simply handle actions. +//! +//! # Examples +//! +//! For an example of using the [`Parser`] please see the examples folder. The example included +//! there simply logs all the actions [`Perform`] does. One quick thing to see it in action is to +//! pipe `vim` into it +//! +//! ```ignore +//! cargo build --release --example parselog +//! vim | target/release/examples/parselog +//! ``` +//! +//! Just type `:q` to exit. +//! +//! [`Parser`]: struct.Parser.html +//! [`Perform`]: trait.Perform.html +//! [Paul Williams' ANSI parser state machine]: http://vt100.net/emu/dec_ansi_parser mod table; mod definitions; mod utf8; -pub use definitions::{Action, State, unpack}; +use definitions::{Action, State, unpack}; use table::{EXIT_ACTIONS, ENTRY_ACTIONS, STATE_CHANGE}; @@ -28,9 +53,9 @@ impl State { const MAX_INTERMEDIATES: usize = 2; const MAX_PARAMS: usize = 16; -struct VtUtf8Receiver<'a, P: Parser + 'a>(&'a mut P, &'a mut State); +struct VtUtf8Receiver<'a, P: Perform + 'a>(&'a mut P, &'a mut State); -impl<'a, P: Parser> utf8::Receiver for VtUtf8Receiver<'a, P> { +impl<'a, P: Perform> utf8::Receiver for VtUtf8Receiver<'a, P> { fn codepoint(&mut self, c: char) { self.0.print(c); *self.1 = State::Ground; @@ -42,10 +67,10 @@ impl<'a, P: Parser> utf8::Receiver for VtUtf8Receiver<'a, P> { } } -/// ANSI VTE Parser +/// Parser for raw _VTE_ protocol which delegates actions to a [`Perform`] /// -/// As described in http://vt100.net/emu/dec_ansi_parser -pub struct StateMachine { +/// [`Perform`]: trait.Perform.html +pub struct Parser { state: State, intermediates: [u8; MAX_INTERMEDIATES], intermediate_idx: usize, @@ -55,9 +80,10 @@ pub struct StateMachine { utf8_parser: utf8::Parser, } -impl StateMachine { - pub fn new() -> StateMachine { - StateMachine { +impl Parser { + /// Create a new Parser + pub fn new() -> Parser { + Parser { state: State::Ground, intermediates: [0u8; MAX_INTERMEDIATES], intermediate_idx: 0, @@ -68,18 +94,23 @@ impl StateMachine { } } - pub fn params(&self) -> &[i64] { + fn params(&self) -> &[i64] { &self.params[..self.num_params] } - pub fn intermediates(&self) -> &[u8] { + fn intermediates(&self) -> &[u8] { &self.intermediates[..self.intermediate_idx] } - pub fn advance<P: Parser>(&mut self, parser: &mut P, byte: u8) { + /// Advance the parser state + /// + /// Requires a [`Perform`] in case `byte` triggers an action + /// + /// [`Perform`]: trait.Perform.html + pub fn advance<P: Perform>(&mut self, performer: &mut P, byte: u8) { // Utf8 characters are handled out-of-band. if let State::Utf8 = self.state { - self.process_utf8(parser, byte); + self.process_utf8(performer, byte); return; } @@ -94,27 +125,27 @@ impl StateMachine { // Unpack into a state and action let (state, action) = unpack(change); - self.perform_state_change(parser, state, action, byte); + self.perform_state_change(performer, state, action, byte); } #[inline] - fn process_utf8<P>(&mut self, parser: &mut P, byte: u8) - where P: Parser + fn process_utf8<P>(&mut self, performer: &mut P, byte: u8) + where P: Perform { - let mut receiver = VtUtf8Receiver(parser, &mut self.state); + let mut receiver = VtUtf8Receiver(performer, &mut self.state); let utf8_parser = &mut self.utf8_parser; utf8_parser.advance(&mut receiver, byte); } - fn perform_state_change<P>(&mut self, parser: &mut P, state: State, action: Action, byte: u8) - where P: Parser + fn perform_state_change<P>(&mut self, performer: &mut P, state: State, action: Action, byte: u8) + where P: Perform { macro_rules! maybe_action { ($action:expr, $arg:expr) => { match $action { Action::None => (), action => { - self.perform_action(parser, action, $arg); + self.perform_action(performer, action, $arg); }, } } @@ -123,7 +154,7 @@ impl StateMachine { match state { State::Anywhere => { // Just run the action - self.perform_action(parser, action, byte); + self.perform_action(performer, action, byte); }, state => { // Exit action for previous state @@ -142,18 +173,39 @@ impl StateMachine { } } - fn perform_action<P: Parser>(&mut self, parser: &mut P, action: Action, byte: u8) { + fn perform_action<P: Perform>(&mut self, performer: &mut P, action: Action, byte: u8) { match action { - Action::Print => parser.print(byte as char), - Action::Execute => parser.execute(self, byte), - Action::Hook => parser.hook(self, byte), - Action::Put => parser.put(self, byte), - Action::OscStart => parser.osc_start(self, byte), - Action::OscPut => parser.osc_put(self, byte), - Action::OscEnd => parser.osc_end(self, byte), - Action::Unhook => parser.unhook(self, byte), - Action::CsiDispatch => parser.csi_dispatch(self, byte as char), - Action::EscDispatch => parser.esc_dispatch(self, byte), + Action::Print => performer.print(byte as char), + Action::Execute => performer.execute(byte), + Action::Hook => { + performer.hook( + self.params(), + self.intermediates(), + self.ignoring, + byte + ); + }, + Action::Put => performer.put(byte), + Action::OscStart => performer.osc_start(), + Action::OscPut => performer.osc_put(byte), + Action::OscEnd => performer.osc_end(byte), + Action::Unhook => performer.unhook(byte), + Action::CsiDispatch => { + performer.csi_dispatch( + self.params(), + self.intermediates(), + self.ignoring, + byte as char + ); + } + Action::EscDispatch => { + performer.esc_dispatch( + self.params(), + self.intermediates(), + self.ignoring, + byte + ); + }, Action::Ignore | Action::None => (), Action::Collect => { if self.intermediate_idx == MAX_INTERMEDIATES { @@ -187,21 +239,70 @@ impl StateMachine { self.ignoring = false; }, Action::BeginUtf8 => { - self.process_utf8(parser, byte); + self.process_utf8(performer, byte); }, } } } -pub trait Parser { - fn print(&mut self, c: char); - fn execute(&mut self, &StateMachine, byte: u8); - fn hook(&mut self, &StateMachine, byte: u8); - fn put(&mut self, &StateMachine, byte: u8); - fn osc_start(&mut self, &StateMachine, byte: u8); - fn osc_put(&mut self, &StateMachine, byte: u8); - fn osc_end(&mut self, &StateMachine, byte: u8); - fn unhook(&mut self, &StateMachine, byte: u8); - fn csi_dispatch(&mut self, &StateMachine, c: char); - fn esc_dispatch(&mut self, &StateMachine, byte: u8); +/// Performs actions requested by the Parser +/// +/// Actions in this case mean, for example, handling a CSI escape sequence describing cursor +/// movement, or simply printing characters to the screen. +/// +/// The methods on this type correspond to actions described in +/// http://vt100.net/emu/dec_ansi_parser. I've done my best to describe them in +/// a useful way in my own words for completeness, but the site should be +/// referenced if something isn't clear. If the site disappears at some point in +/// the future, consider checking archive.org. +pub trait Perform { + /// Draw a character to the screen and update states + fn print(&mut self, char); + + /// Execute a C0 or C1 control function + fn execute(&mut self, byte: u8); + + /// Invoked when a final character arrives in first part of device control string + /// + /// The control function should be determined from the private marker, final character, and + /// execute with a parameter list. A handler should be selected for remaining characters in the + /// string; the handler function should subsequently be called by `put` for every character in + /// the control string. + /// + /// The `ignore` flag indicates that more than two intermediates arrived and + /// subsequent characters were ignored. + fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, byte: u8); + + /// Pass bytes as part of a device control string to the handle chosen in `hook`. C0 controls + /// will also be passed to the handler. + fn put(&mut self, byte: u8); + + /// Called when a device control string is terminated + /// + /// The previously selected handler should be notified that the DCS has + /// terminated. + fn unhook(&mut self, byte: u8); + + /// Notifies the start of an Operating System Command + fn osc_start(&mut self); + + /// Receives characters for the OSC control string + /// + /// Apparently characters don't need buffering here. + fn osc_put(&mut self, byte: u8); + + /// Called when the OSC has terminated + fn osc_end(&mut self, byte: u8); + + /// A final character has arrived for a CSI sequence + /// + /// The `ignore` flag indicates that more than two intermediates arrived and + /// subsequent characters were ignored. + fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, char); + + /// The final character of an escape sequence has arrived. + /// + /// The `ignore` flag indicates that more than two intermediates arrived and + /// subsequent characters were ignored. + fn esc_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, byte: u8); } |