main
Jordan Orelli 11 months ago
parent 11088138af
commit 5676c6ddcc

@ -1,7 +1,16 @@
/// changes directory
mod cd; mod cd;
/// prints stuff
mod echo; mod echo;
/// prints environment variables
mod printenv; mod printenv;
/// the butt of a file
mod tail; mod tail;
/// locates a binary on our PATH
mod which; mod which;
use crate::{ use crate::{
@ -10,6 +19,7 @@ use crate::{
}; };
use std::collections::HashMap; use std::collections::HashMap;
/// All of the builtin commands that exist
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Builtin { pub enum Builtin {
Changedir, Changedir,
@ -38,6 +48,7 @@ impl Call for Builtin {
} }
} }
/// A mapping of all of the builtin command values to their names
pub fn all() -> HashMap<&'static str, Builtin> { pub fn all() -> HashMap<&'static str, Builtin> {
use Builtin::*; use Builtin::*;
HashMap::from([ HashMap::from([

@ -5,11 +5,12 @@ use crate::{
prompt::Prompt, prompt::Prompt,
}; };
use anyhow::Result;
use std::{cmp::min, io::Write}; use std::{cmp::min, io::Write};
/// The text buffer of a text editor. /// The text buffer of a text editor.
pub struct Buffer { pub struct Buffer {
/// the current contents of the buffer. This is the least efficient way possible to do this. /// the current text of the edit buffer
chars: Vec<char>, chars: Vec<char>,
/// the position of our edit cursor within [Self::chars] /// the position of our edit cursor within [Self::chars]
@ -17,6 +18,7 @@ pub struct Buffer {
} }
impl Buffer { impl Buffer {
/// an empty buffer
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
chars: Vec::new(), chars: Vec::new(),
@ -31,18 +33,21 @@ impl Buffer {
self.show_debug(); self.show_debug();
} }
pub fn pop(&mut self) -> String { /// takes the text out of the editor, leaving an empty buffer in its place
fn pop(&mut self) -> String {
let s: String = self.chars.iter().collect(); let s: String = self.chars.iter().collect();
self.clear(); self.clear();
self.show_debug(); self.show_debug();
s s
} }
pub fn show(&self) -> String { fn show(&self) -> String {
self.chars.iter().collect() self.chars.iter().collect()
} }
pub fn back(&mut self, n: usize) -> bool { /// moves the edit cursor back by a fixed number of characters without modifying the contents
/// of the buffer
fn back(&mut self, n: usize) -> bool {
if self.cursor > 0 { if self.cursor > 0 {
if n > self.cursor { if n > self.cursor {
self.cursor = n; self.cursor = n;
@ -56,7 +61,8 @@ impl Buffer {
} }
} }
pub fn backspace(&mut self) -> bool { /// drops a character from the text buffer at the cursor's location
fn backspace(&mut self) -> bool {
if self.chars.len() > 0 { if self.chars.len() > 0 {
if self.cursor > 0 { if self.cursor > 0 {
self.cursor -= 1; self.cursor -= 1;
@ -70,7 +76,7 @@ impl Buffer {
} }
/// removes the elements left of the cursor, returning the count of removed elements /// removes the elements left of the cursor, returning the count of removed elements
pub fn clear_left(&mut self) -> usize { fn clear_left(&mut self) -> usize {
if self.cursor > 0 { if self.cursor > 0 {
let n = self.chars.drain(..self.cursor).count(); let n = self.chars.drain(..self.cursor).count();
self.cursor = 0; self.cursor = 0;
@ -83,7 +89,7 @@ impl Buffer {
/// moves the edit cursor to the beginning of the buffer. The returned value is the index of /// moves the edit cursor to the beginning of the buffer. The returned value is the index of
/// the edit cursor before the move. /// the edit cursor before the move.
pub fn seek_left(&mut self) -> usize { fn seek_left(&mut self) -> usize {
let n = self.cursor; let n = self.cursor;
self.cursor = 0; self.cursor = 0;
self.show_debug(); self.show_debug();
@ -92,7 +98,7 @@ impl Buffer {
/// moves the edit cursor to the end of the buffer. The returned value is the index of th eedit /// moves the edit cursor to the end of the buffer. The returned value is the index of th eedit
/// cursor before the move. /// cursor before the move.
pub fn seek_right(&mut self) -> usize { fn seek_right(&mut self) -> usize {
let n = self.chars.len() - self.cursor; let n = self.chars.len() - self.cursor;
self.cursor = self.chars.len(); self.cursor = self.chars.len();
self.show_debug(); self.show_debug();
@ -100,7 +106,7 @@ impl Buffer {
} }
/// moves the edit cursor forward by n positions /// moves the edit cursor forward by n positions
pub fn forward(&mut self, n: usize) -> bool { fn forward(&mut self, n: usize) -> bool {
if self.cursor < self.chars.len() { if self.cursor < self.chars.len() {
self.cursor = min(self.chars.len(), self.cursor + n); self.cursor = min(self.chars.len(), self.cursor + n);
self.show_debug(); self.show_debug();
@ -110,13 +116,15 @@ impl Buffer {
} }
} }
pub fn insert(&mut self, c: char) { /// inserts a character into the edit buffer at the cursor's current location
fn insert(&mut self, c: char) {
self.chars.insert(self.cursor, c); self.chars.insert(self.cursor, c);
self.cursor += 1; self.cursor += 1;
self.show_debug(); self.show_debug();
} }
pub fn tail(&self) -> String { /// the portion of the text buffer from the edit cursor to the end of the buffer
fn tail(&self) -> String {
let mut start = self.cursor; let mut start = self.cursor;
if start > 0 { if start > 0 {
start -= 1; start -= 1;
@ -126,11 +134,13 @@ impl Buffer {
chars.iter().collect() chars.iter().collect()
} }
pub fn pos(&self) -> usize { /// the current position of the edit cursor
fn pos(&self) -> usize {
self.cursor self.cursor
} }
pub fn len(&self) -> usize { /// the length of the text buffer
fn len(&self) -> usize {
self.chars.len() self.chars.len()
} }
@ -157,16 +167,22 @@ impl Buffer {
} }
} }
/// Describes how we handle an action from the editor
enum Status { enum Status {
/// Editing is still in progress
Ongoing, Ongoing,
/// A command has been entered in the editor and should be sent to the interpreter
Submit(String), Submit(String),
/// The edit session has ended and the shell should shut down
Done, Done,
} }
/// A primitive terminal-based text editor /// A primitive terminal-based text editor
pub struct Editor { pub struct Editor {
/// the incoming terminal events /// the incoming terminal events
pub(crate) input: input::Reader, input: input::Reader,
/// the in-memory representation of the command that we're currently editing /// the in-memory representation of the command that we're currently editing
buffer: Buffer, buffer: Buffer,
@ -174,11 +190,12 @@ pub struct Editor {
/// our outgoing terminal events, which is used as the display portion of the editor /// our outgoing terminal events, which is used as the display portion of the editor
display: output::Writer, display: output::Writer,
/// this thing draws our prompt
prompt: Prompt, prompt: Prompt,
} }
impl Editor { impl Editor {
pub fn new() -> anyhow::Result<Self> { pub fn new() -> Result<Self> {
Ok(Self { Ok(Self {
input: input::Reader::new()?, input: input::Reader::new()?,
buffer: Buffer::new(), buffer: Buffer::new(),
@ -187,7 +204,9 @@ impl Editor {
}) })
} }
pub fn read_command(&mut self) -> anyhow::Result<Option<String>> { /// reads one command from the editor. This function blocks until the user has entered an
/// entire command.
pub fn read_command(&mut self) -> Result<Option<String>> {
use input::Event::*; use input::Event::*;
loop { loop {
@ -225,7 +244,7 @@ impl Editor {
} }
} }
fn handle_key_press(&mut self, event: crate::key::Event) -> anyhow::Result<Status> { fn handle_key_press(&mut self, event: crate::key::Event) -> Result<Status> {
use crate::key::codes::*; use crate::key::codes::*;
match event { match event {
_ if event.code == ENTER || event.char == '\r' => return self.submit(), _ if event.code == ENTER || event.char == '\r' => return self.submit(),
@ -241,7 +260,7 @@ impl Editor {
Ok(Status::Ongoing) Ok(Status::Ongoing)
} }
fn handle_escape(&mut self, esc: Escape) -> anyhow::Result<Status> { fn handle_escape(&mut self, esc: Escape) -> Result<Status> {
use Escape::*; use Escape::*;
match esc { match esc {
Left => self.back(1)?, Left => self.back(1)?,
@ -253,7 +272,7 @@ impl Editor {
Ok(Status::Ongoing) Ok(Status::Ongoing)
} }
fn handle_control(&mut self, cc: ControlCharacter) -> anyhow::Result<Status> { fn handle_control(&mut self, cc: ControlCharacter) -> Result<Status> {
use ControlCharacter::*; use ControlCharacter::*;
match cc { match cc {
StartOfHeading => self.seek_left()?, StartOfHeading => self.seek_left()?,
@ -267,12 +286,12 @@ impl Editor {
Ok(Status::Ongoing) Ok(Status::Ongoing)
} }
fn insert_dot(&mut self) -> anyhow::Result<()> { fn insert_dot(&mut self) -> Result<()> {
self.insert('\u{2022}')?; self.insert('\u{2022}')?;
Ok(()) Ok(())
} }
fn submit(&mut self) -> anyhow::Result<Status> { fn submit(&mut self) -> Result<Status> {
self.display.newline()?; self.display.newline()?;
let text = self.buffer.pop(); let text = self.buffer.pop();
info!("◇ {}", text); info!("◇ {}", text);
@ -284,7 +303,7 @@ impl Editor {
fn focus_end(&mut self) {} fn focus_end(&mut self) {}
/// moves the edit cursor one character to the left /// moves the edit cursor one character to the left
pub fn back(&mut self, n: usize) -> anyhow::Result<()> { pub fn back(&mut self, n: usize) -> Result<()> {
debug!("⛬ ←"); debug!("⛬ ←");
if self.buffer.back(n) { if self.buffer.back(n) {
self.display.back(n)?; self.display.back(n)?;
@ -293,7 +312,7 @@ impl Editor {
} }
/// moves the edit cursor one character to the right /// moves the edit cursor one character to the right
pub fn forward(&mut self, n: usize) -> anyhow::Result<()> { pub fn forward(&mut self, n: usize) -> Result<()> {
debug!("⛬ →"); debug!("⛬ →");
if self.buffer.forward(n) { if self.buffer.forward(n) {
self.display.forward(n)?; self.display.forward(n)?;
@ -302,7 +321,7 @@ impl Editor {
} }
/// moves the cursor position to the end of the line /// moves the cursor position to the end of the line
pub fn seek_right(&mut self) -> anyhow::Result<()> { pub fn seek_right(&mut self) -> Result<()> {
info!("»"); info!("»");
let n = self.buffer.seek_right(); let n = self.buffer.seek_right();
if n > 0 { if n > 0 {
@ -313,7 +332,7 @@ impl Editor {
} }
/// moves the cursor position to the beginning of the line /// moves the cursor position to the beginning of the line
pub fn seek_left(&mut self) -> anyhow::Result<()> { pub fn seek_left(&mut self) -> Result<()> {
info!("«"); info!("«");
let n = self.buffer.seek_left(); let n = self.buffer.seek_left();
if n > 0 { if n > 0 {
@ -324,7 +343,7 @@ impl Editor {
} }
/// clears the line from the current cursor position to the beginning of the line /// clears the line from the current cursor position to the beginning of the line
pub fn clear_left(&mut self) -> anyhow::Result<()> { pub fn clear_left(&mut self) -> Result<()> {
info!("» clear left"); info!("» clear left");
let n = self.buffer.clear_left(); let n = self.buffer.clear_left();
if n > 0 { if n > 0 {
@ -342,7 +361,7 @@ impl Editor {
/// clears the scrollback buffer, moving the current edit line to the top of the screen, but /// clears the scrollback buffer, moving the current edit line to the top of the screen, but
/// leaving the edit cursor in place /// leaving the edit cursor in place
pub fn clear_screen(&mut self) -> anyhow::Result<()> { pub fn clear_screen(&mut self) -> Result<()> {
info!("» clear"); info!("» clear");
self.display.clear()?; self.display.clear()?;
self.prompt.print(&mut self.display)?; self.prompt.print(&mut self.display)?;
@ -352,14 +371,14 @@ impl Editor {
Ok(()) Ok(())
} }
pub fn show_prompt(&mut self) -> anyhow::Result<()> { pub fn show_prompt(&mut self) -> Result<()> {
self.reset()?; self.reset()?;
self.prompt.print(&mut self.display)?; self.prompt.print(&mut self.display)?;
Ok(()) Ok(())
} }
/// inserts a character at edit cursor's current position /// inserts a character at edit cursor's current position
pub fn insert(&mut self, c: char) -> anyhow::Result<()> { pub fn insert(&mut self, c: char) -> Result<()> {
self.buffer.insert(c); self.buffer.insert(c);
let tail = self.buffer.tail(); let tail = self.buffer.tail();
@ -375,7 +394,7 @@ impl Editor {
Ok(()) Ok(())
} }
pub fn backspace(&mut self) -> anyhow::Result<()> { pub fn backspace(&mut self) -> Result<()> {
if !self.buffer.backspace() { if !self.buffer.backspace() {
return Ok(()); return Ok(());
} }
@ -397,7 +416,7 @@ impl Editor {
Ok(()) Ok(())
} }
pub fn reset(&mut self) -> anyhow::Result<()> { pub fn reset(&mut self) -> Result<()> {
self.buffer.clear(); self.buffer.clear();
self.display.reset()?; self.display.reset()?;
Ok(()) Ok(())

@ -329,12 +329,14 @@ impl From<Console::INPUT_RECORD> for Event {
} }
} }
/// A reference to a target node in a prefix tree, used for manipulating the prefix tree
#[derive(Debug)] #[derive(Debug)]
pub struct EscapeCursor { pub struct EscapeCursor {
target: Rc<EscapeNode>, target: Rc<EscapeNode>,
root: Rc<EscapeNode>, root: Rc<EscapeNode>,
} }
/// A node in a prefix tree. This prefix tree is used to parse strings into escape sequences
#[derive(Debug)] #[derive(Debug)]
pub enum EscapeNode { pub enum EscapeNode {
Root { Root {
@ -436,10 +438,12 @@ impl EscapeCursor {
} }
} }
/// adds a terminal node into the tree as a child of the current node
fn add_terminal(&mut self, c: char, v: Escape) { fn add_terminal(&mut self, c: char, v: Escape) {
self.target.add_child_terminal(c, v); self.target.add_child_terminal(c, v);
} }
/// resets the cursor back to the top of the tree
fn reset(&mut self) { fn reset(&mut self) {
self.target = Rc::clone(&self.root); self.target = Rc::clone(&self.root);
} }
@ -448,6 +452,7 @@ impl EscapeCursor {
Rc::ptr_eq(&self.target, &self.root) Rc::ptr_eq(&self.target, &self.root)
} }
/// inserts a sequence -> Escape mapping into the prefix tree
fn insert(&mut self, sequence: &str, v: Escape) { fn insert(&mut self, sequence: &str, v: Escape) {
self.reset(); self.reset();
let mut chars = sequence.chars().peekable(); let mut chars = sequence.chars().peekable();
@ -462,8 +467,10 @@ impl EscapeCursor {
} }
} }
/// generates a prefix tree used for parsing escape sequences
macro_rules! escapes { macro_rules! escapes {
($($sequence:literal $variant:tt)*) => { ($($sequence:literal $variant:tt)*) => {
/// a parsed escape sequence
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Escape { pub enum Escape {
Empty, Empty,
@ -472,7 +479,10 @@ macro_rules! escapes {
)* )*
} }
pub fn build_prefix_tree() -> EscapeCursor { /// assembles our prefix tree. Generally only needs to be called once. I think this could
/// actually be done in a proc macro that builds the prefix tree at compile time instead of
/// at run time.
fn build_prefix_tree() -> EscapeCursor {
let mut tree = EscapeNode::new(); let mut tree = EscapeNode::new();
$( $(
let v = Escape::$variant; let v = Escape::$variant;
@ -521,7 +531,8 @@ escapes! {
"[53;5u" CtrlShift5 "[53;5u" CtrlShift5
} }
/// a control character /// a control character. Control characters are single individual ascii characters that represent
/// out of band signalling.
#[derive(Debug)] #[derive(Debug)]
pub enum ControlCharacter { pub enum ControlCharacter {
Null, Null,

@ -1,15 +1,18 @@
use std::fmt; use std::fmt;
use windows::Win32::System::Console; use windows::Win32::System::Console;
/// an abstract keycode
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Code { pub struct Code {
/// The integer value of the keycode /// The integer value of the keycode, as defined by the Windows api. These associate to windows
/// virtual key codes.
pub val: u16, pub val: u16,
/// The unicode symbol that represents the keycode, if any /// The unicode symbol that represents the keycode, if any
pub sym: Option<char>, pub sym: Option<char>,
} }
/// An individual keypress event
#[derive(Debug)] #[derive(Debug)]
pub struct Event { pub struct Event {
/// True for key press events, false for key release events /// True for key press events, false for key release events
@ -31,6 +34,7 @@ pub struct Event {
/// relationship to the user's chosen layout. /// relationship to the user's chosen layout.
pub scancode: u16, pub scancode: u16,
/// the untranslated windows virtual keycode
pub keycode: u16, pub keycode: u16,
/// The unicode character of the Event. Note this these correspond to virtual terminal /// The unicode character of the Event. Note this these correspond to virtual terminal
@ -107,6 +111,7 @@ impl fmt::Display for Event {
macro_rules! codes { macro_rules! codes {
($($val:literal $name:ident $sym:literal)*) => { ($($val:literal $name:ident $sym:literal)*) => {
/// stores our key code constants
pub mod codes { pub mod codes {
use super::Code; use super::Code;
$( $(

@ -60,6 +60,8 @@ impl From<Vec<Glyph>> for Lexeme {
} }
} }
/// The smallest lexical unit of our language. A token represents a single element of program
/// source text, such as an identifier or a piece of punctuation.
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum Token { pub enum Token {
@ -103,8 +105,9 @@ impl Token {
} }
} }
/// Tokenizer splits some input [Glyphs] into [Token] values /// Tokenizer splits some input [Glyphs] into [Token] values. The Tokenize has no lookahead or
pub struct Tokenizer<'text> { /// buffering.
struct Tokenizer<'text> {
source: Glyphs<'text>, source: Glyphs<'text>,
} }
@ -199,6 +202,7 @@ impl<'text> Iterator for Tokenizer<'text> {
} }
} }
/// A Lexer is responsible for converting a piece of source text into a sequence of tokens.
pub struct Lexer<'text> { pub struct Lexer<'text> {
source: Tokenizer<'text>, source: Tokenizer<'text>,
lookahead: VecDeque<Token>, lookahead: VecDeque<Token>,
@ -223,7 +227,7 @@ impl<'text> Lexer<'text> {
Ok(true) Ok(true)
} }
pub fn peek_at(&mut self, idx: usize) -> Result<Option<&Token>, LexError> { fn peek_at(&mut self, idx: usize) -> Result<Option<&Token>, LexError> {
self.fill_lookahead(idx + 1)?; self.fill_lookahead(idx + 1)?;
Ok(self.lookahead.get(idx)) Ok(self.lookahead.get(idx))
} }

@ -19,7 +19,10 @@ mod lex;
/// a real primitive line editor /// a real primitive line editor
mod edit; mod edit;
/// logging configuration
mod log; mod log;
/// stdout control, output more generally
mod output; mod output;
/// turns our tokens into parse trees /// turns our tokens into parse trees
@ -43,28 +46,5 @@ fn main() -> anyhow::Result<()> {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
session.enable_logging("~/clyde.log"); session.enable_logging("~/clyde.log");
session.run()?; session.run()
Ok(())
/*
loop {
match session.input.next()? {
input::Event::Key(event) => {
// CTRL-J to draw a cool little dot
if event.ctrl && event.code == key::J {
debug!("⎈ j: dot");
// red bullet
session
.stdout
.write(String::from("\x1b[31m\u{2022}\x1b[0m").as_bytes())?;
continue;
}
warn!("‽ {}", event);
}
input::Event::Up => debug!("⛬ ↑"),
input::Event::Down => debug!("⛬ ↓"),
}
}
*/
} }

@ -72,6 +72,7 @@ impl PartialEq for Node {
} }
} }
/// Iterates through the children of a cursor node, producing new cursor positions.
pub struct ChildIter { pub struct ChildIter {
target: Rc<Node>, target: Rc<Node>,
root: Rc<Node>, root: Rc<Node>,
@ -179,6 +180,7 @@ impl Cursor {
} }
} }
/// The Parser is responsible for turning a sequence of Tokens into a Parse Tree.
pub struct Parser<'text> { pub struct Parser<'text> {
input: Lexer<'text>, input: Lexer<'text>,
output: Cursor, output: Cursor,

@ -1,7 +1,8 @@
use crate::output; use crate::output;
use std::io::Write;
use anyhow::Result; use anyhow::Result;
use std::io::Write;
/// draws the prompt that appears before a user enters a command
pub struct Prompt { pub struct Prompt {
s: String, s: String,
} }
@ -21,12 +22,13 @@ impl Prompt {
pub fn print(&self, output: &mut output::Writer) -> Result<()> { pub fn print(&self, output: &mut output::Writer) -> Result<()> {
match std::env::current_dir() { match std::env::current_dir() {
Ok(d) => { Ok(d) => {
let text = d.to_str().unwrap().to_owned() + &self.s; _ = write!(output, "{cwd}{arrow}", cwd = d.display(), arrow = self.s);
output.write(text.as_bytes())?; // let text = d.to_str().unwrap().to_owned() + &self.s;
// output.write(text.as_bytes())?;
} }
Err(_) => { Err(_) => {
output.write(self.s.as_bytes())?; _ = write!(output, "{}", self.s);
}, }
} }
Ok(()) Ok(())
} }

@ -106,6 +106,8 @@ impl Eval for Command {
} }
} }
/// the TreeBuilder is responsible for converting a parse tree into an abstract syntax tree. This
/// process leaves us with a tree that can be executed directly
struct TreeBuilder { struct TreeBuilder {
visited: HashSet<usize>, visited: HashSet<usize>,
} }

Loading…
Cancel
Save