aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--examples/parselog.rs59
-rw-r--r--src/definitions.rs2
-rw-r--r--src/lib.rs189
4 files changed, 182 insertions, 70 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 3ccedb1..1e38f87 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,
diff --git a/src/lib.rs b/src/lib.rs
index e31ac14..71953fd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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);
}