Compare commits

..

2 Commits

@ -10,12 +10,11 @@ thiserror = "1.0"
macros = { path = "macros" }
dirs = "4"
log = { version = "0.4", features = [ "max_level_debug", "release_max_level_off" ] }
log = "0.4"
[dependencies.windows]
version = "0.44.0"
features = [
"Win32_Foundation",
"Win32_System_Console",
"Win32_UI_Input_KeyboardAndMouse"
]

@ -1,44 +0,0 @@
# clyde
A command-line shell for Windows.
## examples
In all examples, the Clyde prompt is written as follows:
```
```
### Run a built-in command
Built-in commands are invoked by executing them with a bare string
```
c:\dev\clyde ▷ cd src
c:\dev\clyde\src ▷
```
### Run an external command
External commands are invoked in the same way:
```
c:\dev\clyde ▷ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
```
### Run commands back to back
To run two commands back to back, separate them with a semicolon. Note that
this interaction is entirely unconditional: the second command executes
regardless of whether the first command succeeded or not.
```
c:\dev\clyde ▷ echo one; echo two
one
two
```

@ -1,82 +0,0 @@
/// changes directory
mod cd;
mod ls;
/// prints stuff
mod echo;
/// prints environment variables
mod printenv;
mod pwd;
/// removes files
mod rm;
mod mkdir;
/// the butt of a file
mod tail;
/// locates a binary on our PATH
mod which;
use crate::{
error::ExecError,
run::{Call, Context, Value},
};
use std::collections::HashMap;
/// All of the builtin commands that exist
#[derive(Clone, Copy)]
pub enum Builtin {
Changedir,
Echo,
Ls,
Mkdir,
Printenv,
Pwd,
Rm,
Tail,
Which,
}
impl Builtin {
fn resolve(&self) -> &dyn Call {
use Builtin::*;
match self {
Changedir => &cd::Changedir,
Echo => &echo::Echo,
Ls => &ls::Ls,
Mkdir => &mkdir::Mkdir,
Printenv => &printenv::Printenv,
Pwd => &pwd::Pwd,
Rm => &rm::Rm,
Tail => &tail::Tail,
Which => &which::Which,
}
}
}
impl Call for Builtin {
fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
self.resolve().call(ctx, args)
}
}
/// A mapping of all of the builtin command values to their names
pub fn all() -> HashMap<&'static str, Builtin> {
use Builtin::*;
HashMap::from([
("cd", Changedir),
("echo", Echo),
("ls", Ls),
("mkdir", Mkdir),
("printenv", Printenv),
("pwd", Pwd),
("rm", Rm),
("tail", Tail),
("which", Which),
])
}

@ -1,25 +0,0 @@
use crate::{
error::ExecError,
run::{Call, Context, Value},
};
use std::env;
pub struct Changedir;
impl Call for Changedir {
fn call(&self, _ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
match args.len() {
0 => {
todo!()
}
1 => {
let name = args[0].try_as_str()?;
env::set_current_dir(name).map_err(|e| ExecError::Misc(e.to_string()))?;
}
_ => {
todo!()
}
}
Ok(Value::None)
}
}

@ -1,18 +0,0 @@
use crate::{
error::ExecError,
run::{Call, Context, Value},
};
use std::io::Write;
pub struct Echo;
impl Call for Echo {
fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
let args = args
.into_iter()
.map(|v| v.try_as_str())
.collect::<Result<Vec<_>, _>>()?;
_ = write!(ctx.stdout, "{}\n", args.join(" "));
Ok(Value::None)
}
}

@ -1,24 +0,0 @@
use crate::{
error::ExecError,
run::{Call, Context, Value},
};
use std::{env, fs, io::Write};
pub struct Ls;
impl Call for Ls {
fn call(&self, ctx: &mut Context, _args: &[Value]) -> Result<Value, ExecError> {
let cwd = env::current_dir()?;
let dir = fs::read_dir(&cwd)?;
for child in dir {
let child_path = child?.path();
let fname = child_path
.file_name()
.ok_or_else(|| ExecError::new("no file name"))?
.to_str()
.ok_or_else(|| ExecError::new("non-unicode file name"))?;
_ = write!(ctx.stdout, "{}\n", fname);
}
Ok(Value::None)
}
}

@ -1,25 +0,0 @@
use crate::{
error::ExecError,
run::{Call, Context, Value},
};
use std::fs;
pub struct Mkdir;
impl Call for Mkdir {
fn call(&self, _ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
match args.len() {
0 => {
todo!()
}
1 => {
let path = args[0].try_as_str()?;
fs::create_dir(path)?;
}
_ => {
todo!()
}
}
Ok(Value::None)
}
}

@ -1,26 +0,0 @@
use crate::{
error::ExecError,
run::{Call, Context, Value},
};
use std::{env, io::Write};
pub struct Printenv;
impl Call for Printenv {
fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
if args.len() > 0 {
for arg in args {
let name = arg.try_as_str()?;
_ = match env::var(name) {
Ok(val) => writeln!(ctx.stdout, "{}", val),
Err(e) => writeln!(ctx.stderr, "ERROR {}", e),
};
}
} else {
for (name, val) in env::vars() {
_ = writeln!(ctx.stdout, "{name}={val}");
}
}
Ok(Value::None)
}
}

@ -1,15 +0,0 @@
use crate::{
error::ExecError,
run::{Call, Context, Value},
};
use std::{env, io::Write};
pub struct Pwd;
impl Call for Pwd {
fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
let cwd = env::current_dir()?;
_ = write!(ctx.stdout, "{}\n", cwd.display());
Ok(Value::None)
}
}

@ -1,25 +0,0 @@
use crate::{
error::ExecError,
run::{Call, Context, Value},
};
use std::fs;
pub struct Rm;
impl Call for Rm {
fn call(&self, _ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
match args.len() {
0 => {
todo!()
}
1 => {
let path = args[0].try_as_str()?;
fs::remove_file(path).map_err(|err| ExecError::Misc(err.to_string()))?;
}
_ => {
todo!()
}
}
Ok(Value::None)
}
}

@ -1,26 +0,0 @@
use crate::{
error::ExecError,
run::{Call, Context, Value},
};
use std::{env, io::Write};
pub struct Which;
impl Call for Which {
fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
let path = env::var("path").unwrap_or_default();
let dirs: Vec<&str> = path.split(";").collect();
for arg in args {
let name = arg.try_as_str()?;
for d in dirs.iter() {
let dir = std::path::Path::new(d);
let fname = dir.join(name).with_extension("exe");
if fname.exists() && fname.is_file() {
_ = writeln!(ctx.stdout, "{}", fname.to_str().unwrap());
}
}
}
Ok(Value::None)
}
}

@ -1,474 +0,0 @@
use crate::{
history::History,
input::{self, ControlCharacter, Escape},
log::*,
output,
prompt::Prompt,
};
use anyhow::Result;
use std::{cmp::min, io::Write};
/// The text buffer of a text editor.
pub struct Buffer {
/// the current text of the edit buffer
chars: Vec<char>,
/// the position of our edit cursor within [Self::chars]
cursor: usize,
}
impl Buffer {
/// an empty buffer
pub fn new() -> Self {
Self {
chars: Vec::new(),
cursor: 0,
}
}
/// clears our edit buffer
pub fn clear(&mut self) {
self.cursor = 0;
self.chars.clear();
self.show_debug();
}
/// 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();
self.clear();
self.show_debug();
s
}
fn show(&self) -> String {
self.chars.iter().collect()
}
/// 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 n > self.cursor {
self.cursor = n;
} else {
self.cursor -= n;
}
self.show_debug();
true
} else {
false
}
}
/// drops a character from the text buffer at the cursor's location
fn backspace(&mut self) -> bool {
if self.chars.len() > 0 {
if self.cursor > 0 {
self.cursor -= 1;
}
self.chars.remove(self.cursor);
self.show_debug();
true
} else {
false
}
}
/// removes the elements left of the cursor, returning the count of removed elements
fn clear_left(&mut self) -> usize {
if self.cursor > 0 {
let n = self.chars.drain(..self.cursor).count();
self.cursor = 0;
self.show_debug();
n
} else {
0
}
}
/// moves the edit cursor to the beginning of the buffer. The returned value is the index of
/// the edit cursor before the move.
fn seek_left(&mut self) -> usize {
let n = self.cursor;
self.cursor = 0;
self.show_debug();
n
}
/// moves the edit cursor to the end of the buffer. The returned value is the index of th eedit
/// cursor before the move.
fn seek_right(&mut self) -> usize {
let n = self.chars.len() - self.cursor;
self.cursor = self.chars.len();
self.show_debug();
n
}
/// moves the edit cursor forward by n positions
fn forward(&mut self, n: usize) -> bool {
if self.cursor < self.chars.len() {
self.cursor = min(self.chars.len(), self.cursor + n);
self.show_debug();
true
} else {
false
}
}
/// 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.cursor += 1;
self.show_debug();
}
/// 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;
if start > 0 {
start -= 1;
}
let chars = &self.chars[start..];
chars.iter().collect()
}
/// the current position of the edit cursor
fn pos(&self) -> usize {
self.cursor
}
/// the length of the text buffer
fn len(&self) -> usize {
self.chars.len()
}
fn show_debug(&self) {
let before = if self.chars.len() > 0 {
self.chars[0..self.cursor].to_vec().iter().collect()
} else {
String::new()
};
let after = if self.chars.len() > 0 {
self.chars[self.cursor..].to_vec().iter().collect()
} else {
String::new()
};
debug!(
" {: <5} {: >5} {}·{}",
self.cursor,
self.chars.len(),
before,
after
);
}
}
/// Describes how we handle an action from the editor
enum Status {
/// Editing is still in progress
Ongoing,
/// A command has been entered in the editor and should be sent to the interpreter
Submit(String),
/// The edit session has ended and the shell should shut down
Done,
}
/// A primitive terminal-based text editor
pub struct Editor {
/// the incoming terminal events
input: input::Reader,
/// the in-memory representation of the command that we're currently editing
buffer: Buffer,
/// our outgoing terminal events, which is used as the display portion of the editor
display: output::Writer,
/// this thing draws our prompt
prompt: Prompt,
/// Our current position when scrolling through the history. A hist_pos of 0 means that we're
/// not looking at the history. hist_pos of 1 means that we're looking at the first most-recent
/// command entered.
hist_pos: usize,
}
impl Editor {
pub fn new() -> Result<Self> {
Ok(Self {
input: input::Reader::new()?,
buffer: Buffer::new(),
display: output::Writer::stdout()?,
prompt: Prompt::new(),
hist_pos: 0,
})
}
/// reads one command from the editor. This function blocks until the user has entered an
/// entire command.
pub fn read_command(&mut self, history: &mut History) -> Result<Option<String>> {
use input::Event::*;
loop {
let event = self.input.next()?;
let status = match event {
Key(e) => self.handle_key_press(e)?,
Focus(true) => {
self.focus_start();
Status::Ongoing
}
Focus(false) => {
self.focus_end();
Status::Ongoing
}
Control(cc) => self.handle_control(cc)?,
Escape(escape) => self.handle_escape(escape, history)?,
Size => {
debug!("ignoring size event");
Status::Ongoing
}
Menu(_) => {
debug!("ignoring menu event");
Status::Ongoing
}
Mouse { x, y } => {
debug!("ignoring mouse event {x}, {y}");
Status::Ongoing
}
};
match status {
Status::Ongoing => {}
Status::Done => return Ok(None),
Status::Submit(e) => {
self.hist_pos = 0;
return Ok(Some(e));
}
}
}
}
fn handle_key_press(&mut self, event: crate::key::Event) -> Result<Status> {
use crate::key::codes::*;
match event {
_ if event.code == ENTER || event.char == '\r' => return self.submit(),
_ if event.code == TAB || event.char == '\t' => {}
_ if event.char == '\u{7f}' => self.backspace()?,
_ if event.ctrl && event.code == A => self.seek_left()?,
_ if event.ctrl && event.code == D => return Ok(Status::Done),
_ if event.ctrl && event.code == E => self.seek_right()?,
_ if event.ctrl && event.code == U => self.clear_left()?,
_ if event.down && !event.char.is_control() => self.insert(event.char)?,
_ => debug!("ignored key press: {event:?}"),
}
Ok(Status::Ongoing)
}
fn handle_escape(&mut self, esc: Escape, history: &History) -> Result<Status> {
use Escape::*;
match esc {
Left => self.back(1)?,
Right => self.forward(1)?,
Up => self.prev_history(history)?,
Down => self.next_history(history)?,
Home => self.seek_left()?,
End => self.seek_right()?,
esc => debug!(" Ignored escape: {esc:?}"),
}
Ok(Status::Ongoing)
}
fn handle_control(&mut self, cc: ControlCharacter) -> Result<Status> {
use ControlCharacter::*;
match cc {
StartOfHeading => self.seek_left()?,
EndOfTransmission => return Ok(Status::Done),
Enquiry => self.seek_right()?,
Formfeed => self.clear_screen()?,
Linefeed => self.insert_dot()?,
NegativeAcknowledge => self.clear_left()?,
cc => debug!("ignored control character: {cc:?}"),
}
Ok(Status::Ongoing)
}
fn insert_dot(&mut self) -> Result<()> {
self.insert('\u{2022}')?;
Ok(())
}
fn submit(&mut self) -> Result<Status> {
self.display.newline()?;
let text = self.buffer.pop();
info!("◇ {}", text);
Ok(Status::Submit(text))
}
fn focus_start(&mut self) {}
fn focus_end(&mut self) {}
/// moves the edit cursor one character to the left
pub fn back(&mut self, n: usize) -> Result<()> {
debug!("⛬ ←");
if self.buffer.back(n) {
self.display.back(n)?;
}
Ok(())
}
/// moves the edit cursor one character to the right
pub fn forward(&mut self, n: usize) -> Result<()> {
debug!("⛬ →");
if self.buffer.forward(n) {
self.display.forward(n)?;
}
Ok(())
}
/// moves the cursor position to the end of the line
pub fn seek_right(&mut self) -> Result<()> {
info!("»");
let n = self.buffer.seek_right();
if n > 0 {
// move right by the distance seeked
self.display.forward(n)?;
}
Ok(())
}
/// moves the cursor position to the beginning of the line
pub fn seek_left(&mut self) -> Result<()> {
info!("«");
let n = self.buffer.seek_left();
if n > 0 {
// move left by the distance seeked
self.display.back(n)?;
}
Ok(())
}
/// clears the line from the current cursor position to the beginning of the line
pub fn clear_left(&mut self) -> Result<()> {
info!("» clear left");
let n = self.buffer.clear_left();
if n > 0 {
// move left by the number of elements removed
self.display.back(n)?;
// draw the elements remaining, followed by a space for each removed
// element
let kept = self.buffer.show();
let text = format!("{}{:width$}", kept, "", width = n);
self.display.write(text.as_bytes())?;
self.display.back(n + kept.chars().count())?;
}
Ok(())
}
/// clears the scrollback buffer, moving the current edit line to the top of the screen, but
/// leaving the edit cursor in place
pub fn clear_screen(&mut self) -> Result<()> {
info!("» clear");
self.display.clear()?;
self.prompt.print(&mut self.display)?;
self.display.write(self.buffer.show().as_bytes())?;
self.display.back(self.buffer.len() - self.buffer.pos())?;
self.reset()?;
Ok(())
}
pub fn show_prompt(&mut self) -> Result<()> {
self.reset()?;
self.prompt.print(&mut self.display)?;
Ok(())
}
/// inserts a character at edit cursor's current position
pub fn insert(&mut self, c: char) -> Result<()> {
self.buffer.insert(c);
let tail = self.buffer.tail();
let n = tail.chars().count();
// write everything from the current line cursor out to the output buffer.
self.display.write(tail.as_bytes())?;
if n > 1 {
// if we wrote more than one character, because we weren't at the end, we
// need to rewind the terminal cursor to where it was.
self.display.back(n - 1)?;
}
Ok(())
}
pub fn backspace(&mut self) -> Result<()> {
if !self.buffer.backspace() {
return Ok(());
}
// move cursor back two spaces
self.display.back(2)?;
let tail = format!("{} ", self.buffer.tail());
let n = tail.chars().count();
self.display.write(tail.as_bytes())?;
// after writing out the tail, rewind by the number of characters in
// the tail
if n > 1 {
self.display.back(n - 1)?;
} else {
// honestly I can't remember how I figured this out
self.display.write(b" \x1b[1D")?;
}
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
self.buffer.clear();
self.display.reset()?;
Ok(())
}
fn prev_history(&mut self, history: &History) -> Result<()> {
let text = match history.look_back(self.hist_pos + 1) {
Some(s) => {
self.hist_pos += 1;
s
}
None => return Ok(()),
};
self.seek_right()?;
self.clear_left()?;
for c in text.chars() {
self.insert(c)?;
}
Ok(())
}
fn next_history(&mut self, history: &History) -> Result<()> {
if self.hist_pos <= 1 {
self.seek_right()?;
self.clear_left()?;
self.hist_pos = 0;
return Ok(());
}
let text = match history.look_back(self.hist_pos - 1) {
Some(s) => {
self.hist_pos -= 1;
s
}
None => return Ok(()),
};
self.seek_right()?;
self.clear_left()?;
for c in text.chars() {
self.insert(c)?;
}
Ok(())
}
}

@ -1,8 +1,3 @@
use crate::{
lex::Token,
syntax::ExpandError,
topo::{Glyph, Position},
};
use std::io;
use thiserror::Error;
use windows::Win32::Foundation::{GetLastError, BOOL};
@ -14,126 +9,9 @@ pub enum Error {
#[error("i/o error: {0}")]
IOError(#[from] io::Error),
}
#[derive(Error, Debug)]
pub enum InputError {
#[error("unrecognized control character: {0}")]
UnrecognizedControlCharacter(u8),
#[error("input record cannot convert to control character because it is an up event")]
ControlCharactersOnlyGoDownNotUp,
#[error("unrecognized escape sequence: {0}")]
BadEscapeSequence(String),
#[error("fart")]
UnexpectedInputRecordDuringEscapeParting,
}
#[derive(Debug, Error)]
pub enum LexError {
#[error("unexpected character {g} at {pos:?}", g = .0.glyph, pos = .0.position)]
UnexpectedCharacter(Glyph),
#[error("unexpected character {g} at {pos:?} while lexing a bare string", g = .0.glyph, pos = .0.position)]
UnexpectedCharacterInBareString(Glyph),
#[error("unexpected character {g} at {pos:?} while lexing a glob", g = .0.glyph, pos = .0.position)]
UnexpectedCharacterInGlob(Glyph),
#[error("unexpected eof")]
UnexpectedEOF(Position),
#[error("not yet supported: {1}")]
NotYetSupported(Position, String),
}
impl LexError {
pub fn not_yet(p: Position, msg: &str) -> Self {
LexError::NotYetSupported(p, msg.to_string())
}
fn position(&self) -> Position {
use LexError::*;
match self {
UnexpectedCharacter(g) => g.position,
UnexpectedCharacterInBareString(g) => g.position,
UnexpectedCharacterInGlob(g) => g.position,
UnexpectedEOF(p) => *p,
NotYetSupported(p, _) => *p,
}
}
}
#[derive(Debug, Error)]
pub enum ParseError {
#[error("lex error at line {line}, column {col}", line=.0.position().line, col=.0.position().column)]
LexError(#[from] LexError),
#[error("Unexpected Token: {0:?}")]
UnexpectedToken(Token),
#[error("Illegal attempt to climb parse tree while already at root")]
AtRootAlready,
#[error("Illegal attempt to climb barse tree when target parent has already been dropped")]
ParentIsGone,
#[error("Illegal attempt to double-borrow a node")]
BorrowError(#[from] std::cell::BorrowMutError),
#[error("Illegal attempt to push a value as a child to a terminal value")]
PushOntoTerminal,
#[error("Statement node has no children")]
StatementIsEmpty,
#[error("dangling")]
DanglingElements,
#[error("shell expansion error")]
ExpansionError(#[from] ExpandError),
#[error("you wouldn't parse a semicolon")]
WhyParseSemicolon,
}
#[derive(Debug, Error)]
pub enum ExecError {
#[error("failed waiting on child process '{name}' ({pid})")]
ProcessWaitError {
name: String,
pid: u32,
#[source]
source: io::Error,
},
#[error("failed to spawn '{name}' as a child process")]
ProcessSpawnError {
name: String,
#[source]
source: io::Error,
},
#[error("i/o error")]
IOError(#[from] std::io::Error),
#[error("value type error: {0}")]
ValueTypeError(String),
#[error("error: {0}")]
Misc(String),
}
impl ExecError {
pub fn type_error<S: Into<String>>(msg: S) -> Self {
Self::ValueTypeError(msg.into())
}
pub fn new<S: Into<String>>(msg: S) -> Self {
Self::Misc(msg.into())
}
#[error("input error: {0}")]
InputError(String),
}
impl Error {
@ -148,6 +26,10 @@ impl Error {
Err(Error::last_error())
}
}
pub fn input_error<S: Into<String>>(msg: S) -> Self {
Error::InputError(msg.into())
}
}
impl From<Error> for std::io::Error {

@ -0,0 +1,66 @@
mod command;
mod tail;
mod which;
mod printenv;
mod echo;
pub use command::Command;
pub use echo::Echo;
pub use tail::Tail;
pub use which::Which;
pub use printenv::Printenv;
/*
Posix Shell builtins:
alias bg cd command false fc fg getopts hash jobs kill
newgrp pwd read true type ulimit umask unalias wait
Bourne Shell builtins:
: - do nothing except expand arguments and perform redirections
. - read and execute a file in the current shell context
break - exit from a loop
cd - change the current working directory to directory
continue - resume the next iteration of a loop
eval - evaluate the arguments as a single command
exec - replace the shell without creating a new process
exit - exits the process with a status
export - exports environment variables
getopts - used by shell scripts to parse arguments
hash - displays the hash table used by the shell to map command names to files
pwd - prints the absolute path of the current directory
readonly - mark each name as readonly so that they cannot be reassigned
return - causes a shell function to stop execution and return a value
shift - shifts the position parameters to the left by n
test - evaluate a conditional expression and return a status of 0 or 1
times - print out the user and system times used by the shell and its children. sorry, what the fuck is this
trap - execute commands when a given signal is received
umask - set the shell process's file creation mask
unset - clears variable or function names
Bash Builtins:
alias - creates an alias for a command
bind - adds key bindings
builtin - access a builtin function even if it has been masked by another function
caller - tells you what line number you're executing?
command - runs a command with a given name, ignoring shell functions
declare - declare variables and give them attributes
echo - echos the outputs
enable - enable and disable builtin shell commands
help - display help text about builtin commands
let - perform arithmetic on shell variables
local - create local variables
logout - exits a login shell with a status
mapfile - read lines from stdin or a file into an array
printf - formats arguments using a format string
read - reads one line from stdin or from a file
readarray - read lines from stdin into an array or something
source - executes the script in the current shell
type - tells you if a thing is an alias, function, builtin, or file command
typeset - synonym for declare, included for korn shell compatibility
ulimit - provides control over the resources available to the processes started by the shell
unalias - clears an alias
*/

@ -0,0 +1,7 @@
use anyhow::Result;
pub trait Command {
fn name() -> String;
fn create() -> Self;
fn exec(&mut self, args: Vec<&str>) -> Result<bool>;
}

@ -0,0 +1,19 @@
use crate::ext::Command;
use anyhow::Result;
pub struct Echo {}
impl Command for Echo {
fn name() -> String {
String::from("echo")
}
fn create() -> Self {
Self {}
}
fn exec(&mut self, args: Vec<&str>) -> Result<bool> {
println!("{}", args.join(" "));
Ok(true)
}
}

@ -0,0 +1,33 @@
use crate::ext::Command;
use anyhow::Result;
pub struct Printenv {}
impl Command for Printenv {
fn name() -> String {
String::from("printenv")
}
fn create() -> Self {
Self {}
}
fn exec(&mut self, args: Vec<&str>) -> Result<bool> {
if args.len() > 0 {
let name = args[0];
match std::env::var(name) {
Ok(val) => {
println!("{}", val);
Ok(true)
}
Err(e) => {
println!("ERROR {}", e);
Ok(false)
}
}
} else {
println!("which variable you fucking dork");
Ok(false)
}
}
}

@ -1,18 +1,30 @@
use crate::{
error::ExecError,
run::{Call, Context, Value},
ext::Command,
output,
};
use std::{
fs::File,
io::{Read, Seek, SeekFrom, Write},
io::{Seek, SeekFrom, Read, Write},
};
pub struct Tail;
use anyhow::Result;
pub struct Tail { }
impl Command for Tail {
fn name() -> String {
String::from("tail")
}
fn create() -> Self {
Self{ }
}
impl Call for Tail {
fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError> {
fn exec(&mut self, args: Vec<&str>) -> Result<bool> {
let mut stdout = output::Writer::stdout()?;
if args.len() > 0 {
let fname = args[0].try_as_str()?;
let fname = args[0];
match File::options().read(true).open(fname) {
Ok(mut f) => {
_ = f.seek(SeekFrom::End(0));
@ -24,7 +36,7 @@ impl Call for Tail {
if n == 1 {
buf.push(one_byte[0]);
if let Ok(s) = std::str::from_utf8(&buf) {
_ = ctx.stdout.write(s.as_bytes());
stdout.write(s.as_bytes())?;
buf.clear();
}
}
@ -34,10 +46,13 @@ impl Call for Tail {
}
}
Err(e) => {
_ = write!(ctx.stderr, "failed to open file: {}", e);
println!("failed to open file: {}", e);
return Err(e.into());
}
}
} else {
println!("need a file name");
return Ok(false);
}
Ok(Value::None)
}
}

@ -0,0 +1,34 @@
use crate::ext::Command;
use anyhow::Result;
pub struct Which {}
impl Command for Which {
fn name() -> String {
String::from("which")
}
fn create() -> Self {
Self {}
}
fn exec(&mut self, args: Vec<&str>) -> Result<bool> {
if args.len() > 0 {
let path = std::env::var("path").unwrap();
let dirs: Vec<&str> = path.split(";").collect();
for d in dirs {
let dir = std::path::Path::new(d);
let fname = dir.join(args[0]).with_extension("exe");
if fname.exists() && fname.is_file() {
println!("{}", fname.to_str().unwrap());
return Ok(true);
}
}
println!("not found: {}", args[0]);
Ok(false)
} else {
println!("what do you want to look for?");
Ok(false)
}
}
}

@ -1,45 +0,0 @@
pub struct History {
events: Vec<String>,
}
impl History {
pub fn new() -> Self {
Self { events: Vec::new() }
}
pub fn add<S: Into<String>>(&mut self, event: S) {
let text = event.into();
if text.is_empty() {
return;
}
self.events.push(text);
}
pub fn look_back(&self, idx: usize) -> Option<&str> {
if idx == 0 || idx > self.events.len() {
return None;
}
Some(&self.events[self.events.len() - idx])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_history() {
let mut history = History::new();
assert_eq!(history.look_back(1), None);
assert_eq!(history.look_back(0), None);
history.add("Hello");
assert_eq!(history.look_back(1), Some("Hello"));
assert_eq!(history.look_back(0), None);
history.add("Hello again");
assert_eq!(history.look_back(2), Some("Hello"));
assert_eq!(history.look_back(1), Some("Hello again"));
assert_eq!(history.look_back(0), None);
}
}

@ -1,132 +1,132 @@
use crate::{
error::{Error, InputError},
key,
log::*,
};
use crate::{error::Error, key, log::*};
use anyhow::{Context, Result};
use log::error;
use std::{
cell::{RefCell, RefMut},
collections::VecDeque,
fmt,
rc::{Rc, Weak},
};
use windows::Win32::{Foundation::HANDLE, System::Console};
/// retrieves a Windows Console handle using the Win32 apis
fn stdin_handle() -> Result<HANDLE> {
unsafe {
let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE)
.context("unable to get stdin handle")?;
Ok(handle)
use windows::Win32::Foundation::HANDLE;
use windows::Win32::System::Console;
use macros::escapes;
#[allow(dead_code)]
fn log_input_mode(mode: Console::CONSOLE_MODE) {
// Characters read by the ReadFile or ReadConsole function are written to the active screen
// buffer as they are typed into the console. This mode can be used only if the
// ENABLE_LINE_INPUT mode is also enabled.
if (mode & Console::ENABLE_ECHO_INPUT).0 > 0 {
debug!("Echo Input: Enabled");
} else {
debug!("Echo Input: Disabled");
}
}
/// checks to see whether the raw underling console input record is the beginning of an escape
/// sequence
fn is_escape_start(record: &Console::INPUT_RECORD) -> bool {
if record.EventType as u32 == Console::KEY_EVENT {
unsafe {
let event = record.Event.KeyEvent;
event.wVirtualKeyCode == 0 && event.uChar.UnicodeChar == 27 && event.bKeyDown.as_bool()
}
// When enabled, text entered in a console window will be inserted at the current cursor
// location and all text following that location will not be overwritten. When disabled, all
// following text will be overwritten.
if (mode & Console::ENABLE_INSERT_MODE).0 > 0 {
debug!("Insert Mode: Enabled");
} else {
false
debug!("Insert Mode: Disabled");
}
}
/// checks to see if a record is indicating the end of an escape sequence
fn is_escape_done(record: &Console::INPUT_RECORD) -> bool {
if record.EventType as u32 == Console::KEY_EVENT {
unsafe {
let event = record.Event.KeyEvent;
// This is a key up event for the physical escape key
event.wVirtualKeyCode == 27
&& event.uChar.UnicodeChar == 27
&& !event.bKeyDown.as_bool()
}
// The ReadFile or ReadConsole function returns only when a carriage return character is read.
// If this mode is disabled, the functions return when one or more characters are available.
if (mode & Console::ENABLE_LINE_INPUT).0 > 0 {
debug!("Line Input Mode: Enabled");
} else {
false
debug!("Line Input Mode: Disabled");
}
}
/// attempts to convert a record into a character that would appear in an escape sequence
fn as_escape_character(record: &Console::INPUT_RECORD) -> Option<char> {
if record.EventType as u32 != Console::KEY_EVENT {
None
// If the mouse pointer is within the borders of the console window and the window has the
// keyboard focus, mouse events generated by mouse movement and button presses are placed in
// the input buffer. These events are discarded by ReadFile or ReadConsole, even when this mode
// is enabled. The ReadConsoleInput function can be used to read MOUSE_EVENT input records from
// the input buffer.
if (mode & Console::ENABLE_MOUSE_INPUT).0 > 0 {
debug!("Mouse Input: Enabled");
} else {
unsafe {
let n = record.Event.KeyEvent.uChar.UnicodeChar as u32;
let c = char::from_u32(n);
c
}
debug!("Mouse Input: Disabled");
}
// CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer
// is being read by ReadFile or ReadConsole, other control keys are processed by the system and
// are not returned in the ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is
// also enabled, backspace, carriage return, and line feed characters are handled by the
// system.
if (mode & Console::ENABLE_PROCESSED_INPUT).0 > 0 {
debug!("Processed Input: Enabled");
} else {
debug!("Processed Input: Disabled");
}
// This flag enables the user to use the mouse to select and edit text. To enable this mode,
// use ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS. To disable this mode, use
// ENABLE_EXTENDED_FLAGS without this flag.
if (mode & Console::ENABLE_QUICK_EDIT_MODE).0 > 0 {
debug!("Quick Edit Mode: Enabled");
} else {
debug!("Quick Edit Mode: Disabled");
}
// User interactions that change the size of the console screen buffer are reported in the
// console's input buffer. Information about these events can be read from the input buffer by
// applications using the ReadConsoleInput function, but not by those using ReadFile or
// ReadConsole.
if (mode & Console::ENABLE_WINDOW_INPUT).0 > 0 {
debug!("Window Input: Enabled");
} else {
debug!("Window Input: Disabled");
}
// Setting this flag directs the Virtual Terminal processing engine to convert user input
// received by the console window into Console Virtual Terminal Sequences that can be retrieved
// by a supporting application through ReadFile or ReadConsole functions.
//
// The typical usage of this flag is intended in conjunction with
// ENABLE_VIRTUAL_TERMINAL_PROCESSING on the output handle to connect to an application that
// communicates exclusively via virtual terminal sequences.
if (mode & Console::ENABLE_VIRTUAL_TERMINAL_INPUT).0 > 0 {
debug!("Virtual Terminal Input: Enabled");
} else {
debug!("Virtual Terminal Input: Disabled");
}
}
/// checks to see if a console record is indicating that the control key has been pressed or
/// depressed. Note that this has no analogue in the vt100 specification, this is purely a Windows
/// thing.
fn as_ctrl_toggle(record: &Console::INPUT_RECORD) -> Option<bool> {
if record.EventType as u32 == Console::KEY_EVENT {
unsafe {
let event = record.Event.KeyEvent;
if event.wVirtualKeyCode == 17 {
return Some(event.bKeyDown.as_bool());
}
}
fn stdin_handle() -> Result<HANDLE> {
unsafe {
let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE)
.context("unable to get stdin handle")?;
Ok(handle)
}
None
}
/// a handle to a terminal's stream of input events. This is analagous to the input portion of a
/// vt100 tty terminal.
///
/// This implementation specifically is implementing the Windows API for Console input.
///
/// In the Windows Console API, console inputs are communicated using struct values having type
/// [INPUT_RECORD](https://learn.microsoft.com/en-us/windows/console/input-record-str).
pub struct Reader {
input: HANDLE,
// this area in memory is where the Windows API will write events read off of the keyboard.
buf: [Console::INPUT_RECORD; 32],
// the number of valid input record items in the buf since last read
buf_len: u32,
// the position of our current indexer into the input record buffer
buf_idx: usize,
/// this scratch buffer is used as a shared memory location for communicating with the kernel.
/// When we request input records, the windows kernel writes them into this buffer.
scratch: [Console::INPUT_RECORD; 32],
/// A lookahead buffer of unprocessed events. If we request input events from Windows and there
/// are more than one event, we put the unprocessed events into this lookahead buffer to reduce
/// the number of syscalls we're making.
lookahead: VecDeque<Console::INPUT_RECORD>,
/// whether or not the control key is pressed, i think?
ctrl: bool,
pub(crate) escapes: EscapeCursor,
}
impl Reader {
pub fn new() -> Result<Self> {
let escapes = build_prefix_tree();
let v = Self {
scratch: [Console::INPUT_RECORD::default(); 32],
lookahead: VecDeque::new(),
buf: [Console::INPUT_RECORD::default(); 32],
buf_len: 0,
buf_idx: 0,
input: stdin_handle()?,
ctrl: false,
escapes,
};
v.reset()?;
Ok(v)
}
pub fn reset(&self) -> Result<()> {
// https://learn.microsoft.com/en-us/windows/console/setconsolemode
let mut mode = Console::CONSOLE_MODE(0);
unsafe {
// set the console's code page to 65001, which is the code page for utf-8
Error::check(Console::SetConsoleCP(65001))?;
Console::SetConsoleCP(65001);
// retrieve the current console mode
Error::check(Console::GetConsoleMode(self.input, &mut mode))?;
let handle = stdin_handle()?;
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
// allow terminal input characters
mode |= Console::ENABLE_VIRTUAL_TERMINAL_INPUT;
@ -143,165 +143,188 @@ impl Reader {
// enable mouse input
mode |= Console::ENABLE_MOUSE_INPUT;
// enable reporting of window resize events
mode |= Console::ENABLE_WINDOW_INPUT;
Error::check(Console::SetConsoleMode(self.input, mode))?;
Error::check(Console::SetConsoleMode(handle, mode))?;
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
// debug!("Stdin details:");
// log_input_mode(mode);
}
Ok(())
}
pub fn next(&mut self) -> Result<Event> {
let record = self.next_record()?;
if let Some(ctrl_toggle) = as_ctrl_toggle(&record) {
self.ctrl = ctrl_toggle;
debug!("ctrl {on}", on = if self.ctrl { "ON" } else { "OFF" });
return self.next();
}
// This is a little weird but on a vt100 terminal, when you held down control you could
// send ascii values directly. So ctrl-a is actually the ascii value of 1, which is the
// "start of heading" character, and ctrl-d is actually the ascii value of 4, which is the
// "end of transmission" character.
//
// Now the way that the Windows Console api works is that it sends events for both key down
// and key up. Here's the rub: the vt100 did not send any key up events. The vt100 didn't
// send anything at all if you tapped and released control, but the Windows Console api
// would send a ctrl key down and ctrl key up event.
if self.ctrl && record.EventType as u32 == Console::KEY_EVENT {
let rec = self.next_rec()?;
if rec.EventType as u32 == Console::KEY_EVENT {
unsafe {
let key_event = record.Event.KeyEvent;
if key_event.bKeyDown.as_bool() {
match ControlCharacter::try_from(key_event) {
Ok(c) => return Ok(Event::Control(c)),
Err(e) => warn!("{:?}", e),
}
let event = rec.Event.KeyEvent;
if event.wVirtualKeyCode == 0 && event.uChar.UnicodeChar == 27 {
return Ok(self.next_escape_sequence()?);
}
}
}
if is_escape_start(&record) {
match self.next_escape_sequence() {
Ok(escape) => return Ok(Event::Escape(escape)),
Err(e) => {
error!("{e}");
return self.next();
if event.wVirtualKeyCode == 17 {
self.ctrl = event.bKeyDown.as_bool();
debug!("ctrl {}", event.bKeyDown.as_bool());
}
}
}
Ok(rec.into())
}
let event: Event = record.into();
if matches!(event, Event::Key(key::Event { down: false, .. })) {
return self.next();
fn log_recs(&mut self) {
debug!(" +---------------------+");
for i in 0..self.buf_len {
let rec = self.buf[i as usize];
let e: Event = rec.into();
match e {
Event::Key(k) => debug!(" | {} |", k),
_ => debug!(" | {:<14?} |", &e),
}
}
Ok(event)
debug!(" +---------------------+");
}
/// reads the next INPUT_RECORD value from the underlying Windows Console file descriptor.
fn next_record(&mut self) -> Result<Console::INPUT_RECORD> {
if let Some(record) = self.lookahead.pop_front() {
return Ok(record);
fn next_rec(&mut self) -> Result<Console::INPUT_RECORD> {
if self.buf_idx as u32 >= self.buf_len {
unsafe {
Error::check(Console::ReadConsoleInputA(
self.input,
&mut self.buf,
&mut self.buf_len,
))?;
}
debug!("• {}", self.buf_len);
self.log_recs();
self.buf_idx = 0;
}
let mut num_read: u32 = 0;
// request records from windows
unsafe {
Error::check(Console::ReadConsoleInputA(
self.input,
&mut self.scratch,
&mut num_read,
))?;
}
let rec = self.buf[self.buf_idx];
self.buf_idx += 1;
return Ok(rec);
}
let records = &self.scratch[0..num_read as usize];
debug!("{num_read} records:");
for record in records {
self.lookahead.push_back(*record);
if record.EventType as u32 == Console::KEY_EVENT {
unsafe {
let key: key::Event = record.Event.KeyEvent.clone().into();
debug!(" {key}");
fn next_escape_sequence(&mut self) -> Result<Event> {
match self.next_escape_char()? {
'[' => match self.next_escape_char()? {
'A' => Ok(Event::Up),
'B' => Ok(Event::Down),
'C' => Ok(Event::Right),
'D' => Ok(Event::Left),
'H' => Ok(Event::Home),
'F' => Ok(Event::End),
'1' => match self.next_escape_char()? {
'3' => match self.next_escape_char()? {
';' => match self.next_escape_char()? {
'5' => match self.next_escape_char()? {
'u' => Ok(Event::Drop(String::from("13;5u"))),
e => Err(Error::input_error(format!("[13;5 unexpected escape char: {}", e)).into()),
}
e => Err(Error::input_error(format!("[13; unexpected escape char: {}", e)).into()),
}
e => Err(Error::input_error(format!("[13 unexpected escape char: {}", e)).into()),
}
'5' => match self.next_escape_char()? {
'~' => Ok(Event::Drop(String::from("[15~ - F5"))),
e => Err(Error::input_error(format!("[15 unexpected escape char: {}", e)).into()),
}
'7' => match self.next_escape_char()? {
'~' => Ok(Event::Drop(String::from("[17~ - F6"))),
e => Err(Error::input_error(format!("[17 unexpected escape char: {}", e)).into()),
}
'8' => match self.next_escape_char()? {
'~' => Ok(Event::Drop(String::from("[18~ - F7"))),
e => Err(Error::input_error(format!("[18 unexpected escape char: {}", e)).into()),
}
'9' => match self.next_escape_char()? {
'~' => Ok(Event::Drop(String::from("[19~ - F8"))),
e => Err(Error::input_error(format!("[19 unexpected escape char: {}", e)).into()),
}
e => Err(Error::input_error(format!("[1 unexpected escape char: {}", e)).into()),
},
'2' => match self.next_escape_char()? {
'0' => match self.next_escape_char()? {
'~' => Ok(Event::Drop(String::from("[20~ - F9"))),
e => Err(Error::input_error(format!("[20 unexpected escape char: {}", e)).into()),
}
'1' => match self.next_escape_char()? {
'~' => Ok(Event::Drop(String::from("[21~ - F10"))),
e => Err(Error::input_error(format!("[20 unexpected escape char: {}", e)).into()),
}
'3' => match self.next_escape_char()? {
'~' => Ok(Event::Drop(String::from("[23~ - F11"))),
e => Err(Error::input_error(format!("[23 unexpected escape char: {}", e)).into()),
}
'4' => match self.next_escape_char()? {
'~' => Ok(Event::Drop(String::from("[24~ - F12"))),
e => Err(Error::input_error(format!("[24 unexpected escape char: {}", e)).into()),
}
e => Err(Error::input_error(format!("[2 unexpected escape char: {}", e)).into()),
}
} else {
debug!(" -");
e => Err(Error::input_error(format!("[ unexpected escape char: {}", e)).into()),
},
'O' => match self.next_escape_char()? {
'P' => Ok(Event::Drop(String::from("OP - F1"))),
'Q' => Ok(Event::Drop(String::from("OQ - F2"))),
'R' => Ok(Event::Drop(String::from("OR - F3"))),
'S' => Ok(Event::Drop(String::from("OS - F4"))),
e => Err(Error::input_error(format!("O unexpected escape char: {}", e)).into()),
}
e => Err(Error::input_error(format!("unexpected escape char: {}", e)).into()),
}
Ok(self.lookahead.pop_front().unwrap())
}
fn next_escape_sequence(&mut self) -> Result<Escape> {
self.escapes.reset();
loop {
let record = self.next_record()?;
if is_escape_start(&record) {
continue;
}
if is_escape_done(&record) {
if self.escapes.is_at_root() {
return Ok(Escape::Empty);
} else {
panic!();
}
}
let c = as_escape_character(&record)
.ok_or(InputError::UnexpectedInputRecordDuringEscapeParting)?;
match self.escapes.step(c) {
EscapeStep::Continue => {}
EscapeStep::End(escape) => return Ok(escape),
EscapeStep::Abort(s) => return Err(InputError::BadEscapeSequence(s).into()),
// fn take_bracket(&mut self) -> Result<()> {
// let rec = self.next_rec()?;
// if rec.EventType as u32 != Console::KEY_EVENT {
// Err(Error::input_error("failed to read escape sequence: not a key event").into())
// } else {
// unsafe {
// let event = rec.Event.KeyEvent;
// if event.wVirtualKeyCode == 0 && event.uChar.UnicodeChar == 91 {
// Ok(())
// } else {
// Err(Error::input_error("failed to read escape sequence: not a [").into())
// }
// }
// }
// }
fn next_escape_char(&mut self) -> Result<char> {
let rec = self.next_rec()?;
if rec.EventType as u32 != Console::KEY_EVENT {
Err(
Error::input_error("failed to read char in escape sequence: not a key event")
.into(),
)
} else {
unsafe {
let n = rec.Event.KeyEvent.uChar.UnicodeChar as u32;
let c = char::from_u32(n);
c.ok_or_else(|| {
let msg = format!("escape key value is not a valid unicode character: {}", n);
Error::input_error(msg).into()
})
}
}
}
}
/// Event represents all of the events that may be seen as input by the shell process. Note that
/// the set of events described here includes both in-band and out of band events, which is because
/// that's how it works in the vt100 spec. It's a little funky but that's because it's reflecting a
/// spec from the 70's.
#[derive(Debug)]
pub enum Event {
/// The process has received focus
Focus(bool),
/// This is a windows Menu event. This might be skippable?
Menu(u32),
/// A Key press. Key events associate to keys on the keyboard that are typically in-band, that
/// is, they're associated with specific characters
Key(key::Event),
/// an event from the user's mouse, such as a mouse movement or click
Mouse { x: i16, y: i16 },
/// a resize event to inform us that the visual window of our process has changed
Size,
/// A decoded ANSI Escape Sequence. At the Terminal level, an Escape sequence is defined by a
/// string of characters instead of just one character. Escape Sequences are used to
/// communicate a variety of edit-related functionality such as navigation.
Escape(Escape),
/// An ASCII Controll Character.
Control(ControlCharacter),
Left,
Right,
Up,
Down,
Home,
End,
Drop(String),
}
impl fmt::Display for Event {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Event::*;
match self {
Key(k) => write!(f, "Event<Key {k}>"),
Control(c) => write!(f, "Event<Control {c:?}>"),
Escape(e) => write!(f, "Event<Escape {e:?}>"),
Focus(true) => write!(f, "Event<focus>"),
Focus(false) => write!(f, "Event<unfocus>"),
Menu(n) => write!(f, "Event<Menu {n}>"),
Mouse { x, y } => write!(f, "Event<Mouse {x},{y}>"),
Size => write!(f, "Event<Size>"),
}
}
}
const ALT_KEYS: u32 = 0x0002 | 0x0001;
const CTRL_KEYS: u32 = 0x0008 | 0x0004;
const SHIFT_PRESSED: u32 = 0x0010;
impl From<Console::INPUT_RECORD> for Event {
fn from(rec: Console::INPUT_RECORD) -> Self {
@ -316,7 +339,24 @@ impl From<Console::INPUT_RECORD> for Event {
let event = rec.Event.MenuEvent;
Event::Menu(event.dwCommandId)
},
Console::KEY_EVENT => unsafe { Event::Key(rec.Event.KeyEvent.clone().into()) },
Console::KEY_EVENT => {
// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
unsafe {
let event = rec.Event.KeyEvent;
let mstate = event.dwControlKeyState;
let c = char::from_u32(event.uChar.UnicodeChar as u32).unwrap_or('💀');
Event::Key(key::Event {
down: event.bKeyDown.as_bool(),
repeats: event.wRepeatCount,
code: key::CODES[event.wVirtualKeyCode as usize],
alt: mstate & ALT_KEYS > 0,
ctrl: mstate & CTRL_KEYS > 0,
shift: mstate & SHIFT_PRESSED > 0,
char: c,
})
}
}
Console::MOUSE_EVENT => {
// OK I think it's safe to ignore these events since we're using the terminal
// escape sequences. I think we never see them because terminal.exe is going to
@ -341,206 +381,6 @@ 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, Clone)]
pub struct EscapeCursor {
target: Rc<EscapeNode>,
root: Rc<EscapeNode>,
}
/// A node in a prefix tree. This prefix tree is used to parse strings into escape sequences. This
/// prefix tree is special in that there are no values in the intermediate nodes of the tree.
#[derive(Debug)]
pub enum EscapeNode {
Root {
children: RefCell<Vec<Rc<EscapeNode>>>,
},
Nonterminal {
c: char,
parent: Weak<EscapeNode>,
children: RefCell<Vec<Rc<EscapeNode>>>,
},
Terminal {
c: char,
parent: Weak<EscapeNode>,
v: Escape,
},
}
impl EscapeNode {
/// Creates a new prefix tree mapping character sequences to structured escape codes
pub fn new() -> EscapeCursor {
let root = Rc::new(EscapeNode::Root {
children: RefCell::new(Vec::new()),
});
EscapeCursor {
target: Rc::clone(&root),
root,
}
}
fn char(&self) -> char {
match self {
EscapeNode::Nonterminal { c, .. } | EscapeNode::Terminal { c, .. } => *c,
_ => panic!(),
}
}
fn children(&self) -> RefMut<Vec<Rc<EscapeNode>>> {
match self {
EscapeNode::Root { children } | EscapeNode::Nonterminal { children, .. } => {
children.borrow_mut()
}
_ => panic!(),
}
}
fn parent(&self) -> Option<Rc<EscapeNode>> {
match self {
EscapeNode::Root { .. } => None,
EscapeNode::Nonterminal { parent, .. } => parent.upgrade(),
EscapeNode::Terminal { parent, .. } => parent.upgrade(),
}
}
fn child(&self, c: char) -> Option<Rc<EscapeNode>> {
for child in self.children().iter_mut() {
if child.char() == c {
return Some(Rc::clone(child));
}
}
None
}
}
impl EscapeCursor {
/// advances our cursor by one step. We look at the current position and descend the tree into
/// the node described by the target character. If that node is a terminal node, we return that
/// node's value.
fn step(&mut self, c: char) -> EscapeStep {
let child = match self.target.child(c) {
Some(c) => c,
None => {
let path = self.path();
return EscapeStep::Abort(format!("{path}{c}"));
}
};
match child.as_ref() {
EscapeNode::Terminal { v, .. } => {
self.reset();
EscapeStep::End(*v)
}
_ => {
self.target = child;
EscapeStep::Continue
}
}
}
fn add_step(&mut self, c: char) {
match self.target.child(c) {
Some(child) => self.target = child,
None => {
for child in self.target.clone().children().iter() {
if child.char() == c {
self.target = Rc::clone(child);
return;
}
}
let child = Rc::new(EscapeNode::Nonterminal {
c,
parent: Rc::downgrade(&self.target),
children: RefCell::new(Vec::new()),
});
self.target.children().push(Rc::clone(&child));
self.target = child;
}
}
}
/// adds a terminal node into the tree as a child of the current node
fn add_terminal(&mut self, c: char, v: Escape) {
for child in self.target.children().iter_mut() {
if child.char() == c {
panic!();
}
}
let child = Rc::new(EscapeNode::Terminal {
c,
parent: Rc::downgrade(&self.target),
v,
});
self.target.children().push(child);
}
/// resets the cursor back to the top of the tree
fn reset(&mut self) {
self.target = Rc::clone(&self.root);
}
fn is_at_root(&self) -> bool {
Rc::ptr_eq(&self.target, &self.root)
}
/// inserts a sequence -> Escape mapping into the prefix tree
fn insert(&mut self, sequence: &str, v: Escape) {
self.reset();
let mut chars = sequence.chars().peekable();
loop {
let c = chars.next().unwrap();
if chars.peek().is_none() {
self.add_terminal(c, v);
return;
}
self.add_step(c);
}
}
/// renders as a string the path that takes us to the node to which we are currently pointed
fn path(&self) -> String {
let mut cursor = self.clone();
let mut chars = Vec::new();
while !cursor.is_at_root() {
chars.push(cursor.target.char());
cursor.target = cursor.target.parent().unwrap();
}
chars.iter().rev().collect()
}
}
enum EscapeStep {
Continue,
Abort(String),
End(Escape),
}
/// generates a prefix tree used for parsing escape sequences
macro_rules! escapes {
($($sequence:literal $variant:tt)*) => {
/// a parsed escape sequence
#[derive(Debug, Clone, Copy)]
pub enum Escape {
Empty,
$(
$variant,
)*
}
/// 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 v = Escape::$variant;
tree.insert($sequence, v);
)*
tree.reset();
tree
}
};
}
escapes! {
"[A" Up
"[B" Down
@ -552,119 +392,29 @@ escapes! {
"[5~" PageUp
"[6~" PageDown
"[13;2u" ShiftEnter
"[13;3u" AltEnter
"[13;5u" CtrlEnter
"[13;6u" CtrlShiftEnter
"[13;2u" Shift_Enter
"[13;5u" Ctrl_Enter
"[13;6u" Ctrl_Shift_Enter
"OP" F1
"[1;2P" ShiftF1
"[1;3P" AltF1
"[1;4P" ShiftAltF1
"[1;5P" CtrlF1
"[1;6P" CtrlShiftF1
"[1;8P" CtrlAltShiftF1
"[1;2P" Shift_F1
"[1;2Q" Shift_F2
"[1;5P" Ctrl_F1
"[1;6P" Ctrl_Shift_F1
"[1;3P" Alt_F1
"[1;4P" Shift_Alt_F1
"OP" F1
"OQ" F2
"[1;2Q" ShiftF2
"OR" F3
"OS" F4
"[15~" F5
"[15;2~" ShiftF5
"[15;2~" Shift_F5
"[17~" F6
"[18~" F7
"[19~" F8
"[20~" F9
"[21~" F10
"[23~" F11
"[24~" F12
"[53;5u" CtrlShift5
}
/// a control character. Control characters are single individual ascii characters that represent
/// out of band signalling.
#[derive(Debug)]
pub enum ControlCharacter {
Null,
StartOfHeading,
StartOfText,
EndOfText,
EndOfTransmission,
Enquiry,
Acknowledge,
Bell,
Backspace,
Tab,
Linefeed,
VTab,
Formfeed,
CarriageReturn,
ShiftOut,
ShiftIn,
DataLinkEscape,
DeviceControl1,
DeviceControl2,
DeviceControl3,
DeviceControl4,
NegativeAcknowledge,
SynchronousIdle,
EndOfTransmissionBlock,
Cancel,
EndOfMedium,
Substitute,
Esc,
FileSeparator,
GroupSeparator,
RecordSeparator,
UnitSeparator,
}
impl TryFrom<Console::KEY_EVENT_RECORD> for ControlCharacter {
type Error = InputError;
fn try_from(v: Console::KEY_EVENT_RECORD) -> Result<Self, Self::Error> {
if !v.bKeyDown.as_bool() {
return Err(InputError::ControlCharactersOnlyGoDownNotUp);
}
use ControlCharacter::*;
unsafe {
match v.uChar.AsciiChar.0 {
0 => Ok(Null),
1 => Ok(StartOfHeading),
2 => Ok(StartOfText),
3 => Ok(EndOfText),
4 => Ok(EndOfTransmission),
5 => Ok(Enquiry),
6 => Ok(Acknowledge),
7 => Ok(Bell),
8 => Ok(Backspace),
9 => Ok(Tab),
10 => Ok(Linefeed),
11 => Ok(VTab),
12 => Ok(Formfeed),
13 => Ok(CarriageReturn),
14 => Ok(ShiftOut),
15 => Ok(ShiftIn),
16 => Ok(DataLinkEscape),
17 => Ok(DeviceControl1),
18 => Ok(DeviceControl2),
19 => Ok(DeviceControl3),
20 => Ok(DeviceControl4),
21 => Ok(NegativeAcknowledge),
22 => Ok(SynchronousIdle),
23 => Ok(EndOfTransmissionBlock),
24 => Ok(Cancel),
25 => Ok(EndOfMedium),
26 => Ok(Substitute),
27 => Ok(Esc),
28 => Ok(FileSeparator),
29 => Ok(GroupSeparator),
30 => Ok(RecordSeparator),
31 => Ok(UnitSeparator),
n => Err(InputError::UnrecognizedControlCharacter(n)),
}
}
}
"[53;5u" Ctrl_Shift_5
}
/*
*/

@ -1,128 +0,0 @@
use crate::{
edit,
history::History,
log::*,
output,
run::{self, Eval},
syntax::parse,
};
use std::{
error::Error,
io::{self, Write},
path::{Path, PathBuf},
};
use anyhow::Result;
use dirs;
/// An interactive session. The Session object is the top-level object used to control an
/// interactive terminal session.
pub struct Session {
pub editor: edit::Editor,
pub stdout: output::Writer,
pub stderr: output::Writer,
}
impl Session {
pub fn new() -> Result<Self> {
Ok(Self {
editor: edit::Editor::new()?,
stdout: output::Writer::stdout()?,
stderr: output::Writer::stderr()?,
})
}
pub fn run(mut self) -> Result<()> {
let mut state = run::State::new();
let mut ctx = run::Context {
stdout: self.stdout.clone(),
stderr: self.stderr.clone(),
state: &mut state,
};
let mut history = History::new();
info!("» shell session start --------");
loop {
self.editor.show_prompt()?;
let text = match self.editor.read_command(&mut history)? {
Some(text) => text,
None => break,
};
history.add(text.clone());
let command = match parse(&text) {
Ok(ast) => ast,
Err(e) => {
self.render_error(e)?;
continue;
}
};
if let Err(e) = command.eval(&mut ctx) {
self.render_error(e)?;
}
}
info!("» exit");
self.stdout.newline()?;
Ok(())
}
pub fn enable_logging<P>(&self, path: P)
where
P: AsRef<Path>,
{
let log_path = self.expand_path(path);
match Log::file(log_path) {
Ok(f) => {
let target = Box::leak(Box::new(f));
_ = set_logger(target).map(|()| set_max_level(LevelFilter::Debug));
}
Err(e) => {
println!("did not open log file: {}", e);
}
}
}
pub fn expand_path<P: AsRef<Path>>(&self, p: P) -> PathBuf {
let p = p.as_ref();
match p.to_str() {
Some(s) => {
if s.len() == 0 || !s.starts_with('~') {
return p.to_path_buf();
}
}
None => {
return p.to_path_buf();
}
}
let mut parts = p.components();
let first = parts.next().unwrap().as_os_str().to_str().unwrap();
if first == "~" {
dirs::home_dir().unwrap().join(parts)
} else {
let my_home = dirs::home_dir().unwrap();
let home_root = my_home.parent().unwrap();
let first = &first[1..];
let other_home = home_root.join(first);
other_home.join(parts)
}
}
pub fn render_error<E: Error>(&mut self, e: E) -> io::Result<()> {
self.render_error_helper(e, 0)?;
Ok(())
}
fn render_error_helper<E: Error>(&mut self, e: E, depth: u8) -> io::Result<()> {
if depth > 0 {
writeln!(self.stdout, " {e}")?;
} else {
writeln!(self.stdout, "{e}:")?;
}
if let Some(cause) = e.source() {
self.render_error_helper(cause, depth + 1)?;
}
Ok(())
}
}

@ -1,18 +1,14 @@
use std::fmt;
use windows::Win32::System::Console;
/// an abstract keycode
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Code {
/// The integer value of the keycode, as defined by the Windows api. These associate to windows
/// virtual key codes.
/// The integer value of the keycode
pub val: u16,
/// The unicode symbol that represents the keycode, if any
pub sym: Option<char>,
}
/// An individual keypress event
#[derive(Debug)]
pub struct Event {
/// True for key press events, false for key release events
@ -21,26 +17,10 @@ pub struct Event {
/// The number of times this event has happened in sequence
pub repeats: u16,
/// The virtual keycode for the keyboard event. This represents the associated character
/// pressed on a keyboard, not the physical button. For example, on an AZERTY keyboard,
/// pressing the key whose legend says A but is in the position of the Q key on a QWERTY
/// keyboard,
///
/// For more info, see here:
/// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
/// The virtual key code for the keyboard event
pub code: Code,
/// The hardware-provided scancode. This is the location of the physical button, with no
/// relationship to the user's chosen layout.
pub scancode: u16,
/// the untranslated windows virtual keycode
pub keycode: u16,
/// The unicode character of the Event. Note this these correspond to virtual terminal
/// sequences. For example, when you press F1, you get an escape sequence of [OP, you'll see
/// the chars O and P but not related to their keys, because they're virtual keys. Hey wait a
/// second, that means I have to be folding the escape sequences into events.
/// The unicode character, or 💀 if not applicable
pub char: char,
/// Whether or not one of the CTRL keys was held when the event was triggered
@ -53,135 +33,74 @@ pub struct Event {
pub shift: bool,
}
// HEY where did these come from who left these here
const ALT_KEYS: u32 = 0x0002 | 0x0001;
const CTRL_KEYS: u32 = 0x0008 | 0x0004;
const SHIFT_PRESSED: u32 = 0x0010;
impl From<Console::KEY_EVENT_RECORD> for Event {
fn from(record: Console::KEY_EVENT_RECORD) -> Self {
unsafe {
let mstate = record.dwControlKeyState;
let c = char::from_u32(record.uChar.UnicodeChar as u32).unwrap_or('💀');
let keycode = codes::lookup(record.wVirtualKeyCode as usize);
Self {
down: record.bKeyDown.as_bool(),
repeats: record.wRepeatCount,
code: keycode,
scancode: record.wVirtualScanCode,
keycode: record.wVirtualKeyCode,
alt: mstate & ALT_KEYS > 0,
ctrl: mstate & CTRL_KEYS > 0,
shift: mstate & SHIFT_PRESSED > 0,
char: c,
}
}
}
}
impl fmt::Display for Event {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let down = if self.down { '↓' } else { '↑' };
let sym = match self.code.sym {
Some(c) => c,
None => '·',
None => '∅',
};
let ctrl = if self.ctrl { 'C' } else { '·' };
let alt = if self.alt { 'A' } else { '·' };
let shift = if self.shift { 'S' } else { '·' };
let glyph = if !self.char.is_control() {
self.char
} else if self.char as u32 == 27 {
'⁝'
} else {
'·'
};
write!(
f,
"{down} {ctrl} {alt} {shift} {glyph} {charcode: <3} {sym} {code: <3} {keycode: <3} {scancode: <3}",
charcode = self.char as u32,
code = self.code.val,
keycode = self.keycode,
scancode = self.scancode,
)
let ctrl = if self.ctrl { '⎈' } else { '·' };
let alt = if self.alt { '⎇' } else { '·' };
let shift = if self.shift { '⇧'} else { '·' };
let c = if self.char.is_control() { '·' } else { self.char };
write!(f, "{} {} {} {} {: >3} {} {: >1} {: >3}", down, ctrl, alt, shift, self.code.val, sym, c, self.char as u16)
}
}
macro_rules! codes {
/// CODES contains a lookup table for key codes. Note that this is a sparse array and not all
/// values associate to valid key codes.
pub static CODES: [Code; 256] = gen_codes();
macro_rules! keycodes {
($($val:literal $name:ident $sym:literal)*) => {
const fn gen_codes() -> [Code; 256] {
let mut codes = [Code{val: 0, sym: None}; 256];
let mut i = 0 as usize;
while i < 256 {
codes[i] = Code{val: i as u16, sym: None};
i = i + 1;
}
/// stores our key code constants
pub mod codes {
use super::Code;
$(
#[allow(unused)]
pub const $name: Code = Code{val: $val, sym: Some($sym)};
codes[$val] = Code{val: $val, sym: Some($sym)};
)*
/// generates a table of key codes
/// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
const fn gen_codes() -> [Code; 256] {
let mut codes = [Code{val: 0, sym: None}; 256];
let mut i = 0 as usize;
while i < 256 {
codes[i] = Code{val: i as u16, sym: None};
i = i + 1;
}
$(
codes[$val] = Code{val: $val, sym: Some($sym)};
)*
codes
}
/// Lookup table to retrieve keycodes by their numeric value
const INDEX: [Code; 256] = gen_codes();
/// Translates the numeric value of a win32 VirtualKeyCode into a key::Code value
pub const fn lookup(vk_code: usize) -> Code {
INDEX[vk_code]
}
codes
}
$(
#[allow(dead_code)]
pub const $name: Code = Code{val: $val, sym: Some($sym)};
)*
}
}
codes! {
0x01 MOUSE_LEFT ' '
0x02 MOUSE_RIGHT ' '
0x03 CTRL_BREAK ' ' // control-break processing
0x04 MIDDLE_MOUSE ' '
0x05 X1_MOUSE ' '
0x06 X2_MOUSE ' ' // 0x07 is reserved
keycodes! {
0x07 NOTBACKSPACE '⌫'
0x08 BACKSPACE '⌫'
0x09 TAB '↹' // 0x0A-0B Reserved
0x0C CLEAR ' '
0x0D ENTER '↩' // 0x0E-0F Unassigned
0x09 TAB '↹'
// 0x0A-0B Reserved
// 0x0C CLEAR
0x0D ENTER '↩'
// 0x0E-0F Undefined
0x10 SHIFT '⇧'
0x11 CTRL '⎈'
0x12 ALT '⎇'
0x13 PAUSE '⎉'
0x13 BREAK '⎉'
0x14 CAPS_LOCK '⇪'
0x15 KANA_MODE ' '
// 0x15 IME Kana mode
// 0x15 IME Hanguel mode (maintained for compatibility; use VK_HANGUL)
// 0x15 IME Hangul mode
// 0x16 IME On
// 0x17 IME Junja mode
// 0x18 IME final mode
// 0x19 IME Hanja mode
// 0x19 IME Kanji mode
// 0x1A IME Off
// 0x1C IME convert
0x1B ESC '⎋'
0x20 SPACE '␣'
0x21 PAGE_UP '↟'
0x22 PAGE_DOWN '↡'
0x23 END '⇲'
0x25 LEFT '←'
0x26 UP '↑'
0x27 RIGHT '→'
0x28 DOWN '↓'
0x30 NUM_0 '0'
0x31 NUM_1 '1'
0x32 NUM_2 '2'
0x33 NUM_3 '3'
0x34 NUM_4 '4'
0x35 NUM_5 '5'
0x36 NUM_6 '6'
0x37 NUM_7 '7'
0x38 NUM_8 '8'
0x39 NUM_9 '9'
0x41 A 'a'
0x42 B 'b'
0x43 C 'c'
@ -208,8 +127,144 @@ codes! {
0x58 X 'x'
0x59 Y 'y'
0x5A Z 'z'
0x30 NUM_0 '0'
0x31 NUM_1 '1'
0x32 NUM_2 '2'
0x33 NUM_3 '3'
0x34 NUM_4 '4'
0x35 NUM_5 '5'
0x36 NUM_6 '6'
0x37 NUM_7 '7'
0x38 NUM_8 '8'
0x39 NUM_9 '9'
// 0x70 F1
// 0x71 F2
// 0x72 F3
// 0x73 F4
// 0x74 F5
// 0x75 F6
// 0x76 F7
// 0x77 F8
// 0x78 F9
// 0x79 F10
// 0x7A F11
// 0x7B F12
// 0x7C F13
// 0x7D F14
// 0x7E F15
// 0x7F F16
// 0x80 F17
// 0x81 F18
// 0x82 F19
// 0x83 F20
// 0x84 F21
// 0x85 F22
// 0x86 F23
// 0x87 F24
0x21 PAGE_UP '↟'
0x22 PAGE_DOWN '↡'
0x25 LEFT '←'
0x26 UP '↑'
0x27 RIGHT '→'
0x28 DOWN '↓'
0xA0 LEFT_SHIFT '⇧'
0xA1 RIGHT_SHIFT '⇧'
0xBE PERIOD '.'
0xDE QUOTE '\''
}
/*
VK_NONCONVERT 0x1D IME nonconvert
VK_ACCEPT 0x1E IME accept
VK_MODECHANGE 0x1F IME mode change request
VK_HOME 0x24 HOME key
VK_SELECT 0x29 SELECT key
VK_PRINT 0x2A PRINT key
VK_EXECUTE 0x2B EXECUTE key
VK_SNAPSHOT 0x2C PRINT SCREEN key
VK_INSERT 0x2D INS key
VK_DELETE 0x2E DEL key
VK_HELP 0x2F HELP key
- 0x3A-40 Undefined
VK_LWIN 0x5B Left Windows key (Natural keyboard)
VK_RWIN 0x5C Right Windows key (Natural keyboard)
VK_APPS 0x5D Applications key (Natural keyboard)
- 0x5E Reserved
VK_SLEEP 0x5F Computer Sleep key
VK_NUMPAD0 0x60 Numeric keypad 0 key
VK_NUMPAD1 0x61 Numeric keypad 1 key
VK_NUMPAD2 0x62 Numeric keypad 2 key
VK_NUMPAD3 0x63 Numeric keypad 3 key
VK_NUMPAD4 0x64 Numeric keypad 4 key
VK_NUMPAD5 0x65 Numeric keypad 5 key
VK_NUMPAD6 0x66 Numeric keypad 6 key
VK_NUMPAD7 0x67 Numeric keypad 7 key
VK_NUMPAD8 0x68 Numeric keypad 8 key
VK_NUMPAD9 0x69 Numeric keypad 9 key
VK_MULTIPLY 0x6A Multiply key
VK_ADD 0x6B Add key
VK_SEPARATOR 0x6C Separator key
VK_SUBTRACT 0x6D Subtract key
VK_DECIMAL 0x6E Decimal key
VK_DIVIDE 0x6F Divide key
- 0x88-8F Unassigned
VK_NUMLOCK 0x90 NUM LOCK key
VK_SCROLL 0x91 SCROLL LOCK key
0x92-96 OEM specific
- 0x97-9F Unassigned
VK_LCONTROL 0xA2 Left CONTROL key
VK_RCONTROL 0xA3 Right CONTROL key
VK_LMENU 0xA4 Left ALT key
VK_RMENU 0xA5 Right ALT key
VK_BROWSER_BACK 0xA6 Browser Back key
VK_BROWSER_FORWARD 0xA7 Browser Forward key
VK_BROWSER_REFRESH 0xA8 Browser Refresh key
VK_BROWSER_STOP 0xA9 Browser Stop key
VK_BROWSER_SEARCH 0xAA Browser Search key
VK_BROWSER_FAVORITES 0xAB Browser Favorites key
VK_BROWSER_HOME 0xAC Browser Start and Home key
VK_VOLUME_MUTE 0xAD Volume Mute key
VK_VOLUME_DOWN 0xAE Volume Down key
VK_VOLUME_UP 0xAF Volume Up key
VK_MEDIA_NEXT_TRACK 0xB0 Next Track key
VK_MEDIA_PREV_TRACK 0xB1 Previous Track key
VK_MEDIA_STOP 0xB2 Stop Media key
VK_MEDIA_PLAY_PAUSE 0xB3 Play/Pause Media key
VK_LAUNCH_MAIL 0xB4 Start Mail key
VK_LAUNCH_MEDIA_SELECT 0xB5 Select Media key
VK_LAUNCH_APP1 0xB6 Start Application 1 key
VK_LAUNCH_APP2 0xB7 Start Application 2 key
- 0xB8-B9 Reserved
VK_OEM_1 0xBA Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key
VK_OEM_PLUS 0xBB For any country/region, the '+' key
VK_OEM_COMMA 0xBC For any country/region, the ',' key
VK_OEM_MINUS 0xBD For any country/region, the '-' key
VK_OEM_PERIOD 0xBE For any country/region, the '.' key
VK_OEM_2 0xBF Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key
VK_OEM_3 0xC0 Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key
- 0xC1-D7 Reserved
- 0xD8-DA Unassigned
VK_OEM_4 0xDB Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key
VK_OEM_5 0xDC Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key
VK_OEM_6 0xDD Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key
VK_OEM_7 0xDE Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key
VK_OEM_8 0xDF Used for miscellaneous characters; it can vary by keyboard.
- 0xE0 Reserved
0xE1 OEM specific
VK_OEM_102 0xE2 The <> keys on the US standard keyboard, or the \\| key on the non-US 102-key keyboard
0xE3-E4 OEM specific
VK_PROCESSKEY 0xE5 IME PROCESS key
0xE6 OEM specific
VK_PACKET 0xE7 Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP
- 0xE8 Unassigned
0xE9-F5 OEM specific
VK_ATTN 0xF6 Attn key
VK_CRSEL 0xF7 CrSel key
VK_EXSEL 0xF8 ExSel key
VK_EREOF 0xF9 Erase EOF key
VK_PLAY 0xFA Play key
VK_ZOOM 0xFB Zoom key
VK_NONAME 0xFC Reserved
VK_PA1 0xFD PA1 key
VK_OEM_CLEAR 0xFE Clear key
*/

@ -1,380 +0,0 @@
use crate::{
error::LexError,
topo::{Glyph, Glyphs, Position},
};
use std::{collections::VecDeque, fmt, ops::Range};
/// A Lexeme is the text of a given [Token]. The lexeme contains no information about the Token's
/// meaning or type; it is just a fancy string with position information.
#[derive(PartialEq, Clone)]
pub struct Lexeme {
elems: Vec<Glyph>,
}
impl Lexeme {
/// The area of the input text covered by this lexeme. This might return none of the Lexeme is
/// an empty string.
fn span(&self) -> Option<Range<Position>> {
if self.elems.is_empty() {
return None;
}
Some(Range {
start: self.elems[0].position,
end: self.elems[self.elems.len() - 1].position,
})
}
fn text(&self) -> String {
self.elems.as_slice().iter().map(|tg| tg.glyph).collect()
}
}
impl fmt::Debug for Lexeme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let span = match self.span() {
Some(span) => span,
None => return write!(f, "<empty Lexeme>"),
};
write!(
f,
"<{text} @{start_line}:{start_column}-{end_line}:{end_column}>",
start_line = span.start.line,
start_column = span.start.column,
end_line = span.end.line,
end_column = span.end.column,
text = self.text(),
)
}
}
impl fmt::Display for Lexeme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.text())
}
}
impl From<Vec<Glyph>> for Lexeme {
fn from(v: Vec<Glyph>) -> Self {
Self { elems: v }
}
}
/// 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)]
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
/// A bare word: a sequence of characters without any quotes. A bare word is always not a glob.
Word(Lexeme),
/// A bare word containing 1 or more of the special characters ? or *
Glob(Lexeme),
Semi(Glyph),
Pipe(Glyph),
}
impl Token {
/// Checks whether or not another token has the same contents as this Token. The eq
/// implementation will also consider positional information, whereas the `same` function does
/// not.
#[allow(unused)]
fn same(&self, other: &Self) -> bool {
use Token::*;
println!(
"check same self: {self:?} other: {other:?}",
self = self.text(),
other = other.text()
);
match (self, other) {
(Word(a), Word(b)) => a.text() == b.text(),
(Glob(a), Glob(b)) => a.text() == b.text(),
_ => false,
}
}
fn text(&self) -> String {
use Token::*;
match self {
Word(lexeme) | Glob(lexeme) => lexeme.text(),
Pipe(glyph) | Semi(glyph) => String::from(glyph.glyph),
}
}
}
/// Tokenizer splits some input [Glyphs] into [Token] values. The Tokenize has no lookahead or
/// buffering.
struct Tokenizer<'text> {
source: Glyphs<'text>,
}
impl<'text> Tokenizer<'text> {
/// creates a new tokenizer for some given input source text
pub fn new(text: &'text str) -> Self {
Self {
source: Glyphs::new(text),
}
}
/// produces the next token in our input source text. If we've hit EOF, this will return None.
fn next_token(&mut self) -> Option<Result<Token, LexError>> {
self.source.yeet_whitespace();
let next = self.source.next()?;
match next.glyph {
_ if next.is_word() => Some(self.lex_bare_string(vec![next])),
_ if next.is_glob() => Some(self.lex_glob(vec![next])),
'|' => Some(Ok(Token::Pipe(next))),
'@' => Some(self.lex_var(vec![next])),
'\'' => Some(self.lex_raw_string(vec![next])),
'"' => Some(self.lex_string_literal(vec![])),
';' => Some(Ok(Token::Semi(next))),
_ => Some(Err(LexError::UnexpectedCharacter(next))),
}
}
fn lex_bare_string(&mut self, mut progress: Vec<Glyph>) -> Result<Token, LexError> {
while let Some(next) = self.source.peek() {
match next.glyph {
_ if next.glyph.is_whitespace() => break,
_ if next.is_word() => progress.push(self.source.pop()?),
_ if next.is_glob() => {
progress.push(self.source.pop()?);
return self.lex_glob(progress);
}
';' | '|' => break,
_ => {
return Err(LexError::UnexpectedCharacterInBareString(
self.source.pop()?,
))
}
}
}
if progress.is_empty() {
Err(LexError::UnexpectedEOF(self.source.next_position))
} else {
Ok(Token::Word(progress.into()))
}
}
fn lex_glob(&mut self, mut progress: Vec<Glyph>) -> Result<Token, LexError> {
while let Some(next) = self.source.peek() {
match next.glyph {
';' => break,
_ if next.glyph.is_whitespace() => break,
_ if next.is_glob() => progress.push(self.source.pop()?),
// '\\' => {
// self.source.pop()?;
// progress.push(self.source.pop()?);
// }
_ => return Err(LexError::UnexpectedCharacterInGlob(self.source.pop()?)),
}
}
if progress.is_empty() {
Err(LexError::UnexpectedEOF(self.source.next_position))
} else {
Ok(Token::Glob(progress.into()))
}
}
fn lex_raw_string(&mut self, _progress: Vec<Glyph>) -> Result<Token, LexError> {
Err(LexError::not_yet(
self.source.next_position,
"raw strings not done yet",
))
}
fn lex_string_literal(&mut self, mut progress: Vec<Glyph>) -> Result<Token, LexError> {
while let Some(next) = self.source.peek() {
match next.glyph {
_ if next.glyph.is_whitespace() => progress.push(self.source.pop()?),
_ if next.is_word() => progress.push(self.source.pop()?),
_ if next.is_glob() => progress.push(self.source.pop()?),
'"' => {
self.source.pop()?;
break;
}
_ => todo!(),
}
}
if progress.is_empty() {
Err(LexError::UnexpectedEOF(self.source.next_position))
} else {
Ok(Token::Word(progress.into()))
}
}
fn lex_var(&mut self, _progress: Vec<Glyph>) -> Result<Token, LexError> {
Err(LexError::not_yet(
self.source.next_position,
"variables are not done yet",
))
}
}
impl<'text> Iterator for Tokenizer<'text> {
type Item = Result<Token, LexError>;
fn next(&mut self) -> Option<Self::Item> {
self.next_token()
}
}
/// A Lexer is responsible for converting a piece of source text into a sequence of tokens.
pub struct Lexer<'text> {
source: Tokenizer<'text>,
lookahead: VecDeque<Token>,
}
impl<'text> Lexer<'text> {
pub fn new(source: &'text str) -> Self {
Self {
source: Tokenizer::new(source),
lookahead: VecDeque::new(),
}
}
fn fill_lookahead(&mut self, n: usize) -> Result<bool, LexError> {
while self.lookahead.len() < n {
let token = match self.source.next() {
Some(res) => res?,
None => return Ok(false),
};
self.lookahead.push_back(token);
}
Ok(true)
}
fn peek_at(&mut self, idx: usize) -> Result<Option<&Token>, LexError> {
self.fill_lookahead(idx + 1)?;
Ok(self.lookahead.get(idx))
}
pub fn peek(&mut self) -> Result<Option<&Token>, LexError> {
self.peek_at(0)
}
}
impl<'text> Iterator for Lexer<'text> {
type Item = Result<Token, LexError>;
fn next(&mut self) -> Option<Self::Item> {
match self.lookahead.pop_front() {
Some(token) => Some(Ok(token)),
None => self.source.next(),
}
}
}
/// splits a corpus into Tokens.
#[cfg(test)]
pub fn lex(source: &str) -> Result<Vec<Token>, LexError> {
Lexer::new(source).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::iter::zip;
fn lexeme(txt: &str) -> Lexeme {
let x: Vec<Glyph> = txt
.chars()
.map(|c| Glyph {
glyph: c,
position: Position::origin(),
bytes: 0..0,
})
.collect();
x.into()
}
fn glob(txt: &str) -> Token {
Token::Glob(lexeme(txt))
}
fn word(txt: &str) -> Token {
Token::Word(lexeme(txt))
}
macro_rules! accept {
( $($test_name:ident $input_text:literal [ $( $token:expr )* ])+) => {$(
#[test]
fn $test_name() -> Result<(), LexError> {
#[allow(unused_mut)]
let mut expected: Vec<Token> = Vec::new();
$( expected.push($token); )*
let lexed = lex($input_text)?;
assert_eq!(expected.len(), lexed.len());
for pair in zip(expected, lexed) {
println!("pair: {pair:?}");
assert!(pair.0.same(&pair.1));
}
Ok(())
}
)*};
}
macro_rules! reject {
($($test_name:ident: $input_text:literal;)+) => {$(
#[test]
fn $test_name() {
match lex($input_text) {
Ok(tokens) => {
println!("output tokens: {tokens:?}");
panic!("Did not encounter an expected lex error");
}
Err(e) => println!("output error: {e:?}"),
}
}
)*};
}
reject! {
// Vars aren't done yet
var: "@name";
// Single-quoted strings arne't done yet
strings: r"echo 'one' two";
}
accept! {
empty "" []
spaces " " []
identifier "a" [ word("a") ]
identifier_2 " a" [ word("a") ]
identifier_3 "a " [ word("a") ]
identifier_4 " a " [ word("a") ]
double_quoted_strings r#"echo "one" two"# [
word("echo")
word("one")
word("two")
]
file_name "poop.exe" [ word("poop.exe") ]
multi_idents "one two three four " [
word("one")
word("two")
word("three")
word("four")
]
glob_1 "*" [ glob("*") ]
glob_2 " * " [ glob("*") ]
glob_3 "x*" [ glob("x*") ]
glob_4 "*x" [ glob("*x") ]
glob_5 "*.py" [ glob("*.py") ]
mixed_1 "ls *.py" [ word("ls") glob("*.py") ]
abs "cd c:\\one\\two" [ word("cd") word("c:\\one\\two")]
home "cd ~\\stuff" [ word("cd") word("~\\stuff") ]
}
}

@ -0,0 +1,146 @@
use crate::log::*;
use std::cmp::min;
pub struct Line {
/// the current contents of the line
chars: Vec<char>,
/// the cursor position of our dit head within the vector of characters that we store as chars
cursor: usize,
}
impl Line {
pub fn new() -> Self {
Self {
chars: Vec::new(),
cursor: 0,
}
}
pub fn clear(&mut self) {
self.cursor = 0;
self.chars.clear();
self.show_debug();
}
pub fn pop(&mut self) -> String {
let s: String = self.chars.iter().collect();
self.clear();
self.show_debug();
s
}
pub fn show(&self) -> String {
self.chars.iter().collect()
}
pub fn back(&mut self, n: usize) -> bool {
if self.cursor > 0 {
if n > self.cursor {
self.cursor = n;
} else {
self.cursor -= n;
}
self.show_debug();
true
} else {
false
}
}
pub fn backspace(&mut self) -> bool {
if self.chars.len() > 0 {
if self.cursor > 0 {
self.cursor -= 1;
}
self.chars.remove(self.cursor);
self.show_debug();
true
} else {
false
}
}
/// removes the elements left of the cursor, returning the count of removed elements
pub fn clear_left(&mut self) -> usize {
if self.cursor > 0 {
let n = self.chars.drain(..self.cursor).count();
self.cursor = 0;
self.show_debug();
n
} else {
0
}
}
/// moves the cursor to the start of the edit line, returning the old cursor value
pub fn seek_left(&mut self) -> usize {
let n = self.cursor;
self.cursor = 0;
self.show_debug();
n
}
pub fn seek_right(&mut self) -> usize {
let n = self.chars.len() - self.cursor;
self.cursor = self.chars.len();
self.show_debug();
n
}
pub fn forward(&mut self, n: usize) -> bool {
if self.cursor < self.chars.len() {
self.cursor = min(self.chars.len(), self.cursor + n);
self.show_debug();
true
} else {
false
}
}
pub fn insert(&mut self, c: char) {
self.chars.insert(self.cursor, c);
self.cursor += 1;
self.show_debug();
}
pub fn tail(&self) -> String {
let mut start = self.cursor;
if start > 0 {
start -= 1;
}
let chars = &self.chars[start..];
chars.iter().collect()
}
pub fn pos(&self) -> usize {
self.cursor
}
pub fn len(&self) -> usize {
self.chars.len()
}
fn show_debug(&self) {
let before = if self.chars.len() > 0 {
self.chars[0..self.cursor].to_vec().iter().collect()
} else {
String::new()
};
let after = if self.chars.len() > 0 {
self.chars[self.cursor..].to_vec().iter().collect()
} else {
String::new()
};
debug!(
" {: <5} {: >5} {}·{}",
self.cursor,
self.chars.len(),
before,
after
);
}
}

@ -1,5 +1,5 @@
use crate::error::Error;
pub use log::{debug, info, set_logger, set_max_level, warn, LevelFilter};
pub use log::{debug, error, info, set_logger, set_max_level, trace, warn, LevelFilter};
use std::{
fs::File,
@ -49,19 +49,19 @@ where
match record.level() {
log::Level::Error => {
_ = write!(out, "\x1b[31m{}\x1b[0m\n", record.args());
}
},
log::Level::Warn => {
_ = write!(out, "\x1b[33m{}\x1b[0m\n", record.args());
}
},
log::Level::Info => {
_ = write!(out, "\x1b[37m{}\x1b[0m\n", record.args());
}
},
log::Level::Debug => {
_ = write!(out, "\x1b[90m{}\x1b[0m\n", record.args());
}
},
log::Level::Trace => {
_ = write!(out, "\x1b[36m{}\x1b[0m\n", record.args());
}
},
}
}
}

@ -1,52 +1,205 @@
/// builtin functions
mod builtin;
/// all of the errors for the clyde project live in this module
mod error;
/// handles input from terminals
mod ext;
mod input;
/// defines the interactive mode
mod interactive;
/// key presses, independent from input sources
mod key;
/// lexical analysis
mod lex;
/// a real primitive line editor
mod edit;
mod history;
/// logging configuration
mod line;
mod log;
/// stdout control, output more generally
mod output;
/// turns our tokens into parse trees
mod parse;
/// why is prompt a whole module what am i doing
mod prompt;
mod shell;
use crate::log::*;
use prompt::Prompt;
use shell::Shell;
use std::io::Write;
use anyhow::Result;
fn main() -> Result<()> {
let mut shell = Shell::new()?;
let log_path = shell.expand_path("~/clyde.log");
match Log::file(log_path) {
Ok(f) => {
let target = Box::leak(Box::new(f));
_ = set_logger(target).map(|()| set_max_level(LevelFilter::Debug));
}
Err(e) => {
println!("did not open log file: {}", e);
}
}
let prompt = Prompt::new();
prompt.print(&mut shell.output)?;
info!("» enter");
loop {
match shell.input.next()? {
input::Event::Key(event) => {
if event.down {
if event.code.val == 0 {
debug!(" {}", event);
} else {
warn!(" {}", event);
}
continue;
}
info!(" {}", event);
if event.code == key::LEFT {
shell.back(1)?;
continue;
}
if event.code == key::RIGHT {
shell.forward(1)?;
continue;
}
if event.code == key::ENTER {
shell.output.newline()?;
let s = shell.line.pop();
info!("◇ {}", s);
let tree = parse::parse(&s)?;
debug!(" {:?}", tree);
shell.exec(tree.into())?;
// Some commands don't leave the terminal in a clean state, so we use reset
// to ensure that our input and output modes are what we expect them to be.
shell.reset()?;
prompt.print(&mut shell.output)?;
continue;
}
if event.code == key::TAB {
continue;
}
if event.code == key::BACKSPACE {
if shell.line.backspace() {
// move cursor back two spaces
shell.output.back(2)?;
let tail = format!("{} ", shell.line.tail());
let n = tail.chars().count();
shell.output.write(tail.as_bytes())?;
// after writing out the tail, rewind by the number of characters in
// the tail
if n > 1 {
// let text = format!("\x1b[{}D", n - 1);
shell.output.back(n - 1)?;
// output.write(text.as_bytes())?;
} else {
// honestly I can't remember how I figured this out
shell.output.write(b" \x1b[1D")?;
}
}
continue;
}
// CTRL-D to exit
if event.ctrl && event.code == key::D {
info!("» exit");
shell.output.close()?;
return Ok(());
}
// CTRL-J to draw a cool little dot
if event.ctrl && event.code == key::J {
debug!("⎈ j: dot");
// red bullet
shell
.output
.write(String::from("\x1b[31m\u{2022}\x1b[0m").as_bytes())?;
continue;
}
// CTRL-L to clear the screen
if event.ctrl && event.code == key::L {
info!("» clear");
shell.output.clear()?;
prompt.print(&mut shell.output)?;
shell.output.write(shell.line.show().as_bytes())?;
shell.output.back(shell.line.len() - shell.line.pos())?;
shell.reset()?;
continue;
}
// CTRL-U to erase to the beginning of the line
if event.ctrl && event.code == key::U {
info!("» clear left");
let n = shell.line.clear_left();
if n > 0 {
// move left by the number of elements removed
shell.output.back(n)?;
// draw the elements remaining, followed by a space for each removed
// element
let kept = shell.line.show();
let text = format!("{}{:width$}", kept, "", width = n);
shell.output.write(text.as_bytes())?;
shell.output.back(n + kept.chars().count())?;
}
continue;
}
// CTRL-A to move to the beginning of the line
if event.ctrl && event.code == key::A {
shell.seek_left()?;
continue;
}
// CTRL-E to move to the end of the line
if event.ctrl && event.code == key::E {
shell.seek_right()?;
continue;
}
// TODO: something better here, this is crappy. I should be checking characters
// based on their unicode categories, not this garbo
if !event.char.is_control() {
shell.line.insert(event.char);
let tail = shell.line.tail();
let n = tail.chars().count();
// write everything from the current line cursor out to the output buffer.
shell.output.write(tail.as_bytes())?;
if n > 1 {
// if we wrote more than one character, because we weren't at the end, we
// need to rewind the terminal cursor to where it was.
shell.output.back(n - 1)?;
}
continue;
}
warn!("‽ {}", event);
}
input::Event::Left => shell.back(1)?,
input::Event::Right => shell.forward(1)?,
input::Event::Up => debug!("⛬ ↑"),
input::Event::Down => debug!("⛬ ↓"),
input::Event::Home => shell.seek_left()?,
input::Event::End => shell.seek_right()?,
input::Event::Focus(true) => {}
input::Event::Focus(false) => {}
input::Event::Menu(_command_id) => {}
input::Event::Drop(seq) => debug!("? {}", seq),
input::Event::Mouse { .. } => {}
input::Event::Size => {}
}
}
}
/// the runtime state of the shell process
mod run;
/*
/// syntax and semantic analysis
mod syntax;
> ls
foo
bar
whatever
/// topoglyph is a real word, i promise
mod topo;
> ls
fn main() -> anyhow::Result<()> {
let session = interactive::Session::new()?;
#[cfg(debug_assertions)]
session.enable_logging("~/clyde.log");
session.run()
}
*/

@ -1,9 +1,99 @@
use crate::error::Error;
use crate::{error::Error, log::*};
use anyhow::{Context, Result};
use std::io::{self, Write};
use windows::Win32::{Foundation::HANDLE, System::Console};
use windows::Win32::{
Foundation::{CloseHandle, HANDLE},
System::Console,
};
#[allow(dead_code)]
fn log_output_mode(mode: Console::CONSOLE_MODE) {
// Characters written by the WriteFile or WriteConsole function or echoed by the ReadFile or
// ReadConsole function are parsed for ASCII control sequences, and the correct action is
// performed. Backspace, tab, bell, carriage return, and line feed characters are processed. It
// should be enabled when using control sequences or when ENABLE_VIRTUAL_TERMINAL_PROCESSING is
// set.
if (mode & Console::ENABLE_PROCESSED_OUTPUT).0 > 0 {
debug!("Processed Output: Enabled");
} else {
debug!("Processed Output: Disabled");
}
// When writing with WriteFile or WriteConsole or echoing with ReadFile or ReadConsole, the
// cursor moves to the beginning of the next row when it reaches the end of the current row.
// This causes the rows displayed in the console window to scroll up automatically when the
// cursor advances beyond the last row in the window. It also causes the contents of the
// console screen buffer to scroll up (../discarding the top row of the console screen buffer)
// when the cursor advances beyond the last row in the console screen buffer. If this mode is
// disabled, the last character in the row is overwritten with any subsequent characters.
if (mode & Console::ENABLE_WRAP_AT_EOL_OUTPUT).0 > 0 {
debug!("Wrap at EOL: Enabled");
} else {
debug!("Wrap at EOL: Disabled");
}
// When writing with WriteFile or WriteConsole, characters are parsed for VT100 and similar
// control character sequences that control cursor movement, color/font mode, and other
// operations that can also be performed via the existing Console APIs. For more information,
// see Console Virtual Terminal Sequences.
//
// Ensure ENABLE_PROCESSED_OUTPUT is set when using this flag.
if (mode & Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING).0 > 0 {
debug!("Terminal Processing: Enabled");
} else {
debug!("Terminal Processing: Disabled");
}
// When writing with WriteFile or WriteConsole, this adds an additional state to end-of-line
// wrapping that can delay the cursor move and buffer scroll operations.
//
// Normally when ENABLE_WRAP_AT_EOL_OUTPUT is set and text reaches the end of the line, the
// cursor will immediately move to the next line and the contents of the buffer will scroll up
// by one line. In contrast with this flag set, the cursor does not move to the next line, and
// the scroll operation is not performed. The written character will be printed in the final
// position on the line and the cursor will remain above this character as if
// ENABLE_WRAP_AT_EOL_OUTPUT was off, but the next printable character will be printed as if
// ENABLE_WRAP_AT_EOL_OUTPUT is on. No overwrite will occur. Specifically, the cursor quickly
// advances down to the following line, a scroll is performed if necessary, the character is
// printed, and the cursor advances one more position.
//
// The typical usage of this flag is intended in conjunction with setting
// ENABLE_VIRTUAL_TERMINAL_PROCESSING to better emulate a terminal emulator where writing the
// final character on the screen (../in the bottom right corner) without triggering an
// immediate scroll is the desired behavior.
if (mode & Console::DISABLE_NEWLINE_AUTO_RETURN).0 > 0 {
debug!("Newline Auto Return: Enabled");
} else {
debug!("Newline Auto Return: Disabled");
}
// The APIs for writing character attributes including WriteConsoleOutput and
// WriteConsoleOutputAttribute allow the usage of flags from character attributes to adjust the
// color of the foreground and background of text. Additionally, a range of DBCS flags was
// specified with the COMMON_LVB prefix. Historically, these flags only functioned in DBCS code
// pages for Chinese, Japanese, and Korean languages.
//
// With exception of the leading byte and trailing byte flags, the remaining flags describing
// line drawing and reverse video (../swap foreground and background colors) can be useful for
// other languages to emphasize portions of output.
//
// Setting this console mode flag will allow these attributes to be used in every code page on
// every language.
//
// It is off by default to maintain compatibility with known applications that have
// historically taken advantage of the console ignoring these flags on non-CJK machines to
// store bits in these fields for their own purposes or by accident.
//
// Note that using the ENABLE_VIRTUAL_TERMINAL_PROCESSING mode can result in LVB grid and
// reverse video flags being set while this flag is still off if the attached application
// requests underlining or inverse video via Console Virtual Terminal Sequences.
if (mode & Console::ENABLE_LVB_GRID_WORLDWIDE).0 > 0 {
debug!("LVB Grid: Enabled");
} else {
debug!("LVB Grid: Disabled");
}
}
#[derive(Clone)]
pub struct Writer {
output: HANDLE,
}
@ -13,20 +103,17 @@ impl Writer {
unsafe {
let handle = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE)
.context("unable to get stdout handle")?;
let mut stdout = Self { output: handle };
let mut stdout = Self{output: handle};
stdout.reset()?;
Ok(stdout)
}
}
pub fn stderr() -> Result<Self> {
pub fn close(&mut self) -> Result<()> {
unsafe {
let handle = Console::GetStdHandle(Console::STD_ERROR_HANDLE)
.context("unable to get stdout handle")?;
let mut stdout = Self { output: handle };
stdout.reset()?;
Ok(stdout)
CloseHandle(self.output);
}
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
@ -55,25 +142,23 @@ impl Writer {
Ok(())
}
/// clears the output buffer
pub fn clear(&mut self) -> Result<()> {
self.write(b"\x1b[2J\x1b[0;0H")?;
Ok(())
}
// pub fn hide_cursor(&mut self) -> Result<()> {
// self.write(b"\x1b[?25l")?;
// Ok(())
// }
}
impl Write for Writer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut written = 0;
unsafe {
Error::check(Console::WriteConsoleA(
self.output,
buf,
Some(&mut written),
None,
))?;
Error::check(Console::WriteConsoleA(self.output, buf, None, None))?;
}
Ok(written as usize)
Ok(0)
}
fn flush(&mut self) -> io::Result<()> {

@ -1,352 +1,463 @@
use crate::error::ParseError;
use crate::lex::{Lexer, Token};
use log::debug;
use std::{
fmt,
ops::Deref,
cell::RefCell,
rc::{Rc, Weak},
sync::atomic::AtomicUsize,
rc::Rc,
};
use thiserror::Error;
/// The contents of a node in our parse tree. The parse tree consists of both terminal and
/// nonterminal symbols.
#[derive(Debug, PartialEq)]
pub enum Value {
/// The start symbol of our parse tree. Each parse tree is rooted in a node whose value is the
/// start symbol. This is the only node in the tree that should utilize the start symbol.
Start,
#[derive(Debug, Error)]
pub enum Error {
#[error("Unexpected Token")]
UnexpectedToken,
}
/// The children of a statement symbol make up the components of what will become a statement
/// in our ast
Command,
#[allow(dead_code)]
#[derive(Debug, PartialEq)]
pub enum Token {
Ident(String),
Pipe,
}
/// Each of the tokens from the lex stage becomes a terminal node on our tree
Terminal(Token),
#[allow(dead_code)]
fn lex<S: AsRef<str>>(text: S) -> Result<Vec<Token>, Error> {
Lexer::new(text.as_ref()).lex()
}
impl Value {
fn is_terminal(&self) -> bool {
matches!(self, Value::Terminal(_))
}
struct Lexer<'text> {
chars: std::str::Chars<'text>,
peeked: Option<char>,
}
/// A node in a parse tree.
#[derive(Debug)]
pub struct Node {
pub id: usize,
impl<'text> Lexer<'text> {
fn new(text: &'text str) -> Self {
Self {
chars: text.chars(),
peeked: None,
}
}
/// A node may or may not have a parent node. If a node does not have a parent node, that node
/// is the root node of a tree.
parent: Option<Weak<Node>>,
fn lex(&mut self) -> Result<Vec<Token>, Error> {
let mut tokens = vec![];
while self.peek().is_some() {
self.skip_whitespace();
match self.peek() {
None => return Ok(tokens),
Some('|') => {
tokens.push(Token::Pipe);
self.skip();
}
Some(_) => {
tokens.push(self.ident());
}
}
}
Ok(tokens)
}
/// The value of the element at this node
pub value: Value,
fn skip_whitespace(&mut self) {
loop {
match self.peek() {
None => break,
Some(c) => {
if c.is_whitespace() {
self.skip()
} else {
break;
}
}
}
}
}
/// A node may or may not have children. Since an empty vector is a valid vector, a node
/// without children is represented as having an empty children vector. A node having an empty
/// list of children is a leaf node in a tree.
pub children: RefCell<Vec<Rc<Node>>>,
}
fn peek(&mut self) -> Option<char> {
match self.peeked {
Some(c) => Some(c),
None => match self.chars.next() {
Some(c) => {
self.peeked = Some(c);
Some(c)
}
None => None,
},
}
}
impl Node {
fn new() -> Cursor {
let root = Rc::new(Node {
id: next_id(),
parent: None,
value: Value::Start,
children: RefCell::new(Vec::new()),
});
Cursor {
target: Rc::clone(&root),
prev: root.id,
root,
fn skip(&mut self) {
if self.peeked.is_some() {
self.peeked = None;
} else {
self.chars.next();
}
}
pub fn is_semi(&self) -> bool {
matches!(self.value, Value::Terminal(Token::Semi(_)))
fn ident(&mut self) -> Token {
let mut kept: Vec<char> = vec![];
loop {
match self.peek() {
None => break,
Some(c) => {
if c.is_whitespace() {
break;
}
kept.push(c);
self.skip();
}
}
}
Token::Ident(kept.iter().collect())
}
}
impl PartialEq for Node {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
// struct Parser {
// state: Tree,
// }
//
// impl Parser {
// fn new() -> Self {
// Self {
// state: Tree::new(),
// }
// }
//
// fn parse(mut self, tokens: Vec<Token>) -> Result<Node, Error> {
// Ok(Node::empty())
// // for token in tokens {
// // self.take(token);
// // }
// // let t = self.state.replace(Node::empty());
// // Ok(t.root())
// }
// }
#[derive(PartialEq, Clone)]
pub enum Element {
Empty,
Command(String),
Literal(String),
}
impl fmt::Debug for Element {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
Element::Empty => write!(f, "()"),
Element::Command(cmd) => write!(f, "cmd<{}>", cmd),
Element::Literal(lit) => write!(f, "lit<{}>", lit),
}
}
}
/// Iterates through the children of a cursor node, producing new cursor positions.
pub struct ChildIter {
target: Rc<Node>,
root: Rc<Node>,
idx: usize,
pub struct Node {
pub elem: Element,
pub parent: Option<Rc<RefCell<Node>>>,
pub children: Vec<Rc<RefCell<Node>>>,
}
impl Iterator for ChildIter {
type Item = Cursor;
impl Node {
pub fn new(elem: Element) -> Self {
Self {
elem,
parent: None,
children: Vec::new(),
}
}
fn next(&mut self) -> Option<Self::Item> {
let children = self.target.children.borrow();
let v = children.get(self.idx)?;
self.idx += 1;
Some(Cursor {
target: Rc::clone(v),
root: Rc::clone(&self.root),
prev: self.target.id,
})
pub fn empty() -> Self {
Self::new(Element::Empty)
}
pub fn child_of(parent: Rc<RefCell<Self>>, elem: Element) -> Self {
Self {
elem,
parent: Some(parent),
children: Vec::new(),
}
}
pub fn visit(self) -> Tree {
self.into()
}
}
const LAST_ID: AtomicUsize = AtomicUsize::new(0);
fn next_id() -> usize {
LAST_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{:?}", self.elem)?;
if self.children.len() > 0 {
write!(f, "[")?;
for child in self.children.iter() {
write!(f, "{:?}", child.borrow())?;
}
write!(f, "]")?;
}
Ok(())
}
}
/// Cursor values expose access to a parse tree. A cursor is logically a pointer to a single node
/// within a parse tree. The cursor helps with the ownership structure: so long as there is a
/// cursor to any node on the tree, the tree remains in memory. Once there are no cursors pointed
/// at the tree, it is dropped.
#[derive(Debug, Clone)]
pub struct Cursor {
pub target: Rc<Node>,
root: Rc<Node>,
prev: usize,
pub struct Tree {
target: Rc<RefCell<Node>>,
}
impl Cursor {
/// Climbs one level up a parse tree. The cursor is re-pointed from its current target node to
/// the parent of its current target node. This method fails if the cursor is already at the
/// root node of the parse tree.
pub fn up(&mut self) -> Result<(), ParseError> {
match &self.target.parent {
None => Err(ParseError::AtRootAlready),
Some(parent) => match parent.upgrade() {
Some(parent) => {
self.goto(parent);
Ok(())
}
None => Err(ParseError::ParentIsGone),
},
}
impl Tree {
fn new() -> Self {
Node::empty().into()
}
/// Adds a value to the children of the current target node, then descends to select that
/// child.
fn push(&mut self, v: Value) -> Result<(), ParseError> {
if self.target.value.is_terminal() {
return Err(ParseError::PushOntoTerminal);
/// Adds an element as a node as a child of the current node, then descends the tree to select
/// that child node. This is similar to how pushing a value changes the top element of a stack.
fn push(self, elem: Element) -> Self {
if self.is_empty() {
self.target.replace(Node::new(elem));
self
} else {
let child = Node::child_of(Rc::clone(&self.target), elem);
Tree{target: Rc::new(RefCell::new(child))}
}
let node = Node {
id: next_id(),
parent: Some(Rc::downgrade(&self.target)),
value: v,
children: RefCell::new(Vec::new()),
};
let node = Rc::new(node);
self.target
.children
.try_borrow_mut()?
.push(Rc::clone(&node));
self.goto(node);
Ok(())
}
/// moves the cursor up to the root of the tree in place
pub fn up_to_root(&mut self) {
self.goto(Rc::clone(&self.root));
/// Adds a child node with a given element value to the currently selected node without chaning
/// our selection.
fn append(self, elem: Element) -> Self {
if self.is_empty() {
self.target.replace(Node::new(elem));
} else {
let node = Node::child_of(Rc::clone(&self.target), elem);
let child = Rc::new(RefCell::new(node));
self.target.borrow_mut().children.push(child);
}
self
}
pub fn value(&self) -> &Value {
&self.target.value
/// Tells us whether or not the currently selected node is an empty node
fn is_empty(&self) -> bool {
self.target.borrow().elem == Element::Empty
}
pub fn iter_children(&self) -> ChildIter {
ChildIter {
target: Rc::clone(&self.target),
root: Rc::clone(&self.root),
idx: 0,
}
fn parent(&self) -> Option<Self> {
self.target.borrow().parent.as_ref().map(|parent| Self {
target: Rc::clone(parent),
})
}
#[cfg(test)]
fn is_root(&self) -> bool {
self.target.parent.is_none()
self.parent().is_none()
}
#[cfg(test)]
fn into_root(self) -> Rc<Node> {
Rc::clone(&self.root)
pub fn root(self) -> Self {
match self.parent() {
Some(parent) => parent.root(),
None => self,
}
}
fn text_tree(&self) -> String {
let mut p = self.clone();
p.up_to_root();
let mut buf = String::new();
self.text_tree_helper(0, &mut buf);
buf
fn peek(&self) -> Element {
self.target.borrow().elem.clone()
}
fn text_tree_helper(&self, depth: u32, mut buf: &mut String) {
let line = format!("{:?}\n", self.value());
*buf = buf.to_owned() + &line;
for child in self.iter_children() {
child.text_tree_helper(depth + 1, &mut buf);
fn into_node(self) -> Node {
self.into()
}
fn children(&self) -> ChildIter {
ChildIter {
parent: Rc::clone(&self.target),
idx: 0,
}
}
}
fn goto(&mut self, next: Rc<Node>) {
self.prev = self.target.id;
self.target = next;
impl fmt::Debug for Tree {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{:?}", self.target.borrow())
}
}
/// The Parser is responsible for turning a sequence of Tokens into a Parse Tree.
pub struct Parser<'text> {
input: Lexer<'text>,
output: Cursor,
pub struct ChildIter {
/// pointer to the node in the tree whose children we are looking at.
parent: Rc<RefCell<Node>>,
idx: usize,
}
impl<'text> Parser<'text> {
pub fn new(source: Lexer<'text>) -> Self {
Self {
input: source,
output: Node::new(),
impl Iterator for ChildIter {
type Item = Tree;
fn next(&mut self) -> Option<Self::Item> {
if self.idx >= self.parent.borrow().children.len() {
None
} else {
let child = Rc::clone(&self.parent.borrow().children[self.idx]);
self.idx += 1;
Some(Tree{target: child})
}
}
}
pub fn parse(mut self) -> Result<Cursor, ParseError> {
while self.step()? {}
self.output.up_to_root();
Ok(self.output)
impl From<Tree> for Node {
fn from(value: Tree) -> Self {
value.target.replace(Node::empty())
}
}
fn step(&mut self) -> Result<bool, ParseError> {
match self.output.value() {
Value::Start => self.step_start(),
Value::Command => self.step_statement(),
Value::Terminal(_) => panic!(),
impl From<Node> for Tree {
fn from(value: Node) -> Self {
Self {
target: Rc::new(RefCell::new(value)),
}
}
}
fn step_start(&mut self) -> Result<bool, ParseError> {
assert!(matches!(self.output.value(), Value::Start));
match self.input.peek()? {
Some(Token::Word(_)) => {
self.output.push(Value::Command)?;
let token = self.input.next().unwrap()?;
self.output.push(Value::Terminal(token))?;
self.output.up()?;
Ok(true)
// I don't know how to write a parser lol
// ls
// Command{name: "ls"}
// echo one two three
// Command{name: "echo", args: [Lit("one"), Lit("two"), Lit("three")]}
// echo *.rs
// Command{name: "echo", args: [Glob("*.rs")]}
// Command{name: "echo", args: [Path("main.rs"), Path("parse.rs"), ...]}
// cat main.rs | wc -l
// Pipe{
// src: Command{name: "cat", args: [Lit("main.rs")]}
// dest: Command{name: "wc", args: [Lit("-l")]}
// }
// echo one ; echo two
// Sequence{
// children: [
// Command{Name:
// ]
// }
pub fn parse<S: AsRef<str>>(text: S) -> Result<Tree, Error> {
let mut tree = Tree::new();
for token in lex(text)? {
match (tree.peek(), token) {
(Element::Empty, Token::Ident(cmd)) => {
tree = tree.push(Element::Command(cmd));
}
Some(Token::Glob(_)) => {
let token = self.input.next().unwrap()?;
Err(ParseError::UnexpectedToken(token))
(Element::Command(_), Token::Ident(arg)) => {
tree = tree.append(Element::Literal(arg));
}
Some(Token::Semi(_)) => {
self.output.push(Value::Command)?;
let token = self.input.next().unwrap()?;
self.output.push(Value::Terminal(token))?;
self.output.up()?;
self.output.up()?;
Ok(true)
}
Some(Token::Pipe(_)) => {
let token = self.input.next().unwrap()?;
Err(ParseError::UnexpectedToken(token))
}
None => Ok(false),
_ => todo!(),
}
}
Ok(tree.root())
}
fn step_statement(&mut self) -> Result<bool, ParseError> {
assert!(matches!(self.output.value(), Value::Command));
match self.input.peek()? {
Some(Token::Word(_) | Token::Glob(_)) => {
let token = self.input.next().unwrap()?;
self.output.push(Value::Terminal(token))?;
self.output.up()?;
Ok(true)
}
Some(Token::Pipe(_)) => {
todo!()
}
Some(Token::Semi(_)) => {
let token = self.input.next().unwrap()?;
self.output.push(Value::Terminal(token))?;
self.output.up()?;
self.output.up()?;
Ok(true)
#[cfg(test)]
mod tests {
use super::*;
use Token::Pipe;
fn ident<S: Into<String>>(s: S) -> Token {
Token::Ident(s.into())
}
macro_rules! lex {
(
$($name:ident: $line:literal $($token:expr)* ;)+
) => {
$(
#[test]
fn $name() -> Result<(), Error> {
let mut tokens = lex($line)?;
$(
assert!(tokens.len() > 0);
let front = tokens.remove(0);
assert_eq!($token, front);
)*
Ok(())
}
None => Ok(false),
}
)*
};
}
}
/*
lex! {
single_token:
"one"
ident("one");
a b
command
terminal
a
terminal
b
two_tokens:
"ls one two"
ident("ls") ident("one") ident("two");
a | b
pipeline
command
terminal
a
terminal
|
command
terminal
b
leading_whitespace:
" one"
ident("one");
trailing_whitespace:
"one "
ident("one");
*/
surrounding_whitespace:
" one "
ident("one");
#[cfg(test)]
mod test {
use super::*;
use crate::lex::lex;
internal_hyphen:
"one-two"
ident("one-two");
fn parse(source: &str) -> Result<Cursor, ParseError> {
let tokens = Lexer::new(source);
let parser = Parser::new(tokens);
parser.parse()
pipe:
"|"
Pipe;
pipeline:
"one | two"
ident("one") Pipe ident("two");
pipeline_2:
"one |two"
ident("one") Pipe ident("two");
}
#[test]
fn root() {
let mut cursor = Node::new();
assert!(cursor.up().is_err());
assert!(cursor.target.value == Value::Start);
assert!(cursor.is_root());
fn empty_tree() {
let root = Tree::new().root();
assert!(root.is_root());
assert_eq!(root.peek(), Element::Empty);
assert_eq!(root.children().count(), 0);
}
#[test]
fn single_val() {
let mut cursor = Node::new();
let mut tokens = lex(" ls ").unwrap();
let ls = tokens.pop().unwrap();
assert!(cursor.push(Value::Command).is_ok());
assert!(cursor.push(Value::Terminal(ls.clone())).is_ok());
assert!(cursor.push(Value::Terminal(ls.clone())).is_err());
assert!(cursor.target.value == Value::Terminal(ls));
assert!(!cursor.is_root());
assert!(cursor.up().is_ok());
assert!(cursor.up().is_ok());
assert!(cursor.is_root());
assert!(cursor.up().is_err());
assert!(cursor.value() == &Value::Start);
let root = cursor.into_root();
assert!(root.value == Value::Start);
fn tree_root() {
let root = Tree::new()
.push(Element::Command(String::from("ls")))
.root();
assert!(root.is_root());
assert_eq!(root.children().count(), 0);
assert_eq!(root.into_node().elem, Element::Command(String::from("ls")));
}
#[test]
fn test_parse() -> Result<(), ParseError> {
let x = parse("ls one two three")?;
println!("Parse tree: {x:?}");
// parse("*")?;
// parse("x* ls")?;
Ok(())
fn tree_push() {
let tree = Tree::new()
.push(Element::Command(String::from("ls")));
assert_eq!(tree.peek(), Element::Command(String::from("ls")));
let tree = Tree::new()
.push(Element::Command(String::from("ls")))
.append(Element::Command(String::from("one")));
assert_eq!(tree.peek(), Element::Command(String::from("ls")));
}
#[test]
fn parse_args() {
let res = parse("ls one two three");
assert!(res.is_ok());
let tree = res.unwrap();
assert_eq!(tree.peek(), Element::Command(String::from("ls")));
assert_eq!(tree.children().count(), 3);
let mut args = tree.children();
assert_eq!(args.next().unwrap().peek(), Element::Literal(String::from("one")));
assert_eq!(args.next().unwrap().peek(), Element::Literal(String::from("two")));
assert_eq!(args.next().unwrap().peek(), Element::Literal(String::from("three")));
assert!(args.next().is_none());
}
}

@ -1,8 +1,7 @@
use crate::output;
use anyhow::Result;
use std::io::Write;
use anyhow::Result;
/// draws the prompt that appears before a user enters a command
pub struct Prompt {
s: String,
}
@ -15,11 +14,6 @@ impl Prompt {
// | | +-------- clear styles
// | | | +- show cursor
// v v v v
#[cfg(debug_assertions)]
s: String::from("\x1b[?25l\x1b[31m ▷ \x1b[0m\x1b[?25h"),
#[allow(dead_code)]
#[cfg(not(debug_assertions))]
s: String::from("\x1b[?25l\x1b[32m ▷ \x1b[0m\x1b[?25h"),
}
}
@ -27,13 +21,12 @@ impl Prompt {
pub fn print(&self, output: &mut output::Writer) -> Result<()> {
match std::env::current_dir() {
Ok(d) => {
_ = write!(output, "{cwd}{arrow}", cwd = d.display(), arrow = self.s);
// let text = d.to_str().unwrap().to_owned() + &self.s;
// output.write(text.as_bytes())?;
let text = d.to_str().unwrap().to_owned() + &self.s;
output.write(text.as_bytes())?;
}
Err(_) => {
_ = write!(output, "{}", self.s);
}
output.write(self.s.as_bytes())?;
},
}
Ok(())
}

@ -1,78 +0,0 @@
use crate::{builtin, error::ExecError, output};
use std::{collections::HashMap, process};
/// Eval represents anything that can be evaluated at runtime.
pub trait Eval {
/// Evaluates the receiver, given the current runtime state.
fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError>;
}
/// An individual value at runtime.
#[derive(Debug, Clone)]
pub enum Value {
/// The empty value. This is somewhat analagous to () in Rust or void in C or null in languages
/// that have null.
None,
/// A textual value.
Text(String),
/// The result of having executed some external OS process.
ExitStatus(process::ExitStatus),
}
impl Value {
pub fn try_to_string(self) -> Result<String, ExecError> {
Ok(self.try_as_str()?.to_string())
}
pub fn try_as_str(&self) -> Result<&str, ExecError> {
match self {
Value::None => Err(ExecError::type_error("expected text value, saw None value")),
Value::Text(v) => Ok(&v),
Value::ExitStatus(_) => Err(ExecError::type_error(
"expected text value, saw ExitStatus value",
)),
}
}
}
impl Eval for Value {
fn eval(&self, _: &mut Context) -> Result<Value, ExecError> {
Ok(self.clone())
}
}
/// The state of the runtime. In an environment-passing interpreter, this would be the environment.
/// We avoid the term "environment" because in the context of an OS shell it's confusing whether it
/// means the state of the interpreter itself, or the environment variables of the process. This
/// State type represents the state of the interpreter itself.
pub struct State {
#[allow(unused)]
variables: HashMap<&'static str, Value>,
pub(crate) builtins: HashMap<&'static str, builtin::Builtin>,
}
impl State {
pub fn new() -> Self {
Self {
variables: HashMap::new(),
builtins: builtin::all(),
}
}
pub fn builtin(&self, name: &str) -> Option<builtin::Builtin> {
self.builtins.get(name).copied()
}
}
pub trait Call {
fn call(&self, ctx: &mut Context, args: &[Value]) -> Result<Value, ExecError>;
}
pub struct Context<'s> {
pub(crate) stdout: output::Writer,
pub(crate) stderr: output::Writer,
pub(crate) state: &'s mut State,
}

@ -0,0 +1,162 @@
use crate::{
ext::{Command, Echo, Printenv, Tail, Which},
input,
line::Line,
log::*,
output,
// parse::parse,
parse::{Element, Node},
};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use anyhow::Result;
use dirs;
pub struct Shell {
pub input: input::Reader,
pub output: output::Writer,
pub line: Line,
}
impl Shell {
pub fn new() -> Result<Self> {
Ok(Self {
input: input::Reader::new()?,
output: output::Writer::stdout()?,
line: Line::new(),
})
}
pub fn back(&mut self, n: usize) -> Result<()> {
debug!("⛬ ←");
if self.line.back(n) {
self.output.back(n)?;
}
Ok(())
}
pub fn forward(&mut self, n: usize) -> Result<()> {
debug!("⛬ →");
if self.line.forward(n) {
self.output.forward(n)?;
}
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
self.input.reset()?;
self.output.reset()?;
Ok(())
}
pub fn seek_right(&mut self) -> Result<()> {
info!("» seek right");
let n = self.line.seek_right();
if n > 0 {
// move right by the distance seeked
self.output.forward(n)?;
}
Ok(())
}
pub fn seek_left(&mut self) -> Result<()> {
info!("» seek left");
let n = self.line.seek_left();
if n > 0 {
// move left by the distance seeked
self.output.back(n)?;
}
Ok(())
}
pub fn expand_path<P: AsRef<Path>>(&self, p: P) -> PathBuf {
let p = p.as_ref();
match p.to_str() {
Some(s) => {
if s.len() == 0 || !s.starts_with('~') {
return p.to_path_buf();
}
}
None => {
return p.to_path_buf();
}
}
let mut parts = p.components();
let first = parts.next().unwrap().as_os_str().to_str().unwrap();
if first == "~" {
dirs::home_dir().unwrap().join(parts)
} else {
let my_home = dirs::home_dir().unwrap();
let home_root = my_home.parent().unwrap();
let first = &first[1..];
let other_home = home_root.join(first);
other_home.join(parts)
}
}
pub fn exec(&mut self, root: Node) -> Result<bool> {
match root.elem {
Element::Empty => Ok(true),
Element::Command(cmd) => {
let args: Vec<String> = root
.children
.iter()
.map(|node| match &node.borrow().elem {
Element::Literal(v) => v.clone(),
_ => todo!(),
})
.collect();
self.eval(
cmd.to_string(),
args.iter().map(|arg| arg.as_str()).collect(),
)
}
Element::Literal(_) => todo!(),
}
}
pub fn eval(&mut self, cmd: String, args: Vec<&str>) -> Result<bool> {
match cmd.as_str() {
"pwd" => {
let pb = std::env::current_dir()?;
println!("{}", pb.as_path().as_os_str().to_str().unwrap());
return Ok(true);
}
"cd" => {
let cwd = std::env::current_dir()?;
if args.len() > 0 {
let target = cwd.join(args[0]);
std::env::set_current_dir(target)?;
}
return Ok(true);
}
"printenv" => Printenv::create().exec(args),
"which" => Which::create().exec(args),
"tail" => Tail::create().exec(args),
"echo" => Echo::create().exec(args),
_ => {
let mut proc = std::process::Command::new(cmd);
if args.len() > 0 {
proc.args(args);
}
match proc.spawn() {
Ok(mut child) => {
if let Err(e) = child.wait() {
println!("error: {}", e);
return Err(e.into());
}
}
Err(e) => {
println!("error: {}", e);
return Ok(false);
}
}
return Ok(true);
}
}
}
}

@ -1,252 +0,0 @@
use crate::{
builtin::Builtin,
error::{ExecError, ParseError},
lex::{Lexer, Token},
parse,
run::{Call, Context, Eval, Value},
};
use std::{collections::HashSet, env, ffi::OsString, process};
/// The differnt types of nodes that may appear in our AST
#[derive(Debug)]
pub enum Element {
/// A Block is a list of statements
Block(Block),
/// A Command represents the desire to execute a command
Command(Command),
/// A literal is a ... literal value
Literal(Value),
}
impl Eval for Element {
fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError> {
use Element::*;
match self {
Block(block) => block.eval(ctx),
Command(cmd) => cmd.eval(ctx),
Literal(v) => v.eval(ctx),
}
}
}
#[derive(Debug)]
pub struct Block {
commands: Vec<Command>,
}
impl Block {
pub fn new() -> Self {
Block {
commands: Vec::new(),
}
}
}
impl Eval for Block {
fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError> {
let mut v = Value::None;
for cmd in self.commands.iter() {
v = cmd.eval(ctx)?;
}
Ok(v)
}
}
#[derive(Debug)]
pub struct Command {
name: Box<Element>,
args: Vec<Element>,
}
impl Command {
fn exec_builtin(&self, ctx: &mut Context, builtin: Builtin) -> Result<Value, ExecError> {
let args = self
.args
.iter()
.map(|elem| elem.eval(ctx))
.collect::<Result<Vec<_>, _>>()?;
builtin.call(ctx, &args)
}
fn exec_subprocess(&self, ctx: &mut Context, name: &str) -> Result<Value, ExecError> {
let args = self
.args
.iter()
.map(|elem| elem.eval(ctx)?.try_to_string())
.collect::<Result<Vec<_>, _>>()?;
let mut proc = process::Command::new(&name);
if args.len() > 0 {
proc.args(args);
}
let mut child = proc.spawn().map_err(|e| ExecError::ProcessSpawnError {
name: name.to_string(),
source: e,
})?;
let pid = child.id();
let status = child.wait().map_err(|e| ExecError::ProcessWaitError {
name: name.to_string(),
pid,
source: e,
})?;
Ok(Value::ExitStatus(status))
}
}
impl Eval for Command {
fn eval(&self, ctx: &mut Context) -> Result<Value, ExecError> {
let name = self.name.eval(ctx)?.try_to_string()?;
match ctx.state.builtin(&name) {
Some(builtin) => self.exec_builtin(ctx, builtin),
None => self.exec_subprocess(ctx, &name),
}
}
}
/// 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 {
visited: HashSet<usize>,
}
impl TreeBuilder {
fn new() -> Self {
Self {
visited: HashSet::new(),
}
}
fn parse(&mut self, source: &mut parse::Cursor) -> Result<Element, ParseError> {
let e = match source.value() {
parse::Value::Start => {
let mut root = Block::new();
let children = source.iter_children();
for mut child in children {
if child.target.is_semi() {
continue;
}
let e = self.parse(&mut child)?;
match e {
Element::Command(cmd) => root.commands.push(cmd),
_ => panic!(),
}
}
Element::Block(root)
}
parse::Value::Command => {
let mut children = source.iter_children();
let mut first = match children.next() {
Some(child) => child,
None => return Err(ParseError::StatementIsEmpty),
};
if first.target.is_semi() {
return Err(ParseError::StatementIsEmpty);
}
let name = self.parse(&mut first)?;
let mut cmd = Command {
name: Box::new(name),
args: Vec::new(),
};
// we set complete to true when we find a semicolon. If there are any parse nodes
// that appear -after- a semicolon, that indicates a bug in the prior stage.
let mut complete = false;
for mut child in children {
if child.target.is_semi() {
complete = true;
continue;
}
if complete {
return Err(ParseError::DanglingElements);
} else {
let e = self.parse(&mut child)?;
cmd.args.push(e);
}
}
Element::Command(cmd)
}
parse::Value::Terminal(Token::Word(word)) => {
let text = word.to_string();
let text = expand(&text)?;
Element::Literal(Value::Text(text))
}
parse::Value::Terminal(Token::Glob(_)) => {
todo!()
}
parse::Value::Terminal(Token::Pipe(_)) => {
todo!()
}
parse::Value::Terminal(Token::Semi(_)) => {
return Err(ParseError::WhyParseSemicolon);
}
};
self.visited.insert(source.target.id);
Ok(e)
}
}
pub fn parse(source: &str) -> Result<Element, ParseError> {
let tokens = Lexer::new(source);
let parser = parse::Parser::new(tokens);
let mut parse_tree = parser.parse()?;
let mut builder = TreeBuilder::new();
let root = builder.parse(&mut parse_tree)?;
Ok(root)
}
#[cfg(target_os = "windows")]
fn home_dir() -> Result<OsString, ExpandError> {
env::var_os("HOMEPATH").ok_or(ExpandError::EmptyEnvironmentVar(String::from("HOMEPATH")))
}
#[derive(thiserror::Error, Debug)]
pub enum ExpandError {
#[error("environment var is not set: {0}")]
EmptyEnvironmentVar(String),
#[error("home dir is not utf-8")]
NotUnicode,
}
fn expand(text: &str) -> Result<String, ExpandError> {
let chars = text.chars();
// let mut escape = false;
let mut s = String::new();
for c in chars {
// if !escape && c == '\\' {
// escape = true;
// continue;
// }
// if escape {
// s.push(c);
// escape = false;
// continue;
// }
if c == '~' {
let p = home_dir()?;
s.push_str(p.to_str().ok_or(ExpandError::NotUnicode)?);
continue;
}
s.push(c);
}
Ok(s)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn hi() -> Result<(), ParseError> {
let e = parse("ls one two three")?;
print!("{:?}", e);
Ok(())
}
}

@ -1,207 +0,0 @@
use crate::error::LexError;
use std::{collections::VecDeque, fmt, ops::Range, str::Chars};
/// A position a some two dimensional space defined by lines and columns, specifically concerned
/// with positions of characters within text
#[derive(PartialEq, Clone, Copy)]
pub struct Position {
/// The visual line in which this glyph appears in the source text
pub line: u64,
/// The visual column in which this glyph appears in the source text
pub column: u64,
}
impl Position {
/// The 0,0 position: the very first character of a document is at the 0,0 position
pub fn origin() -> Self {
Self { line: 0, column: 0 }
}
/// Increments position by column, going from the current line,column position to the next
/// column on the same line.
pub fn incr(&mut self) -> Position {
let p = *self;
self.column += 1;
p
}
/// Increments the position by line, going from the current line,column position to the
/// beginning of the next line.
pub fn incr_line(&mut self) -> Position {
let p = *self;
self.column = 0;
self.line += 1;
p
}
}
impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"({line}, {column})",
line = self.line,
column = self.column
)
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
(self as &dyn fmt::Debug).fmt(f)
}
}
/// A Glyph represents a character occurring at some specific position in some input text.
#[derive(PartialEq, Clone)]
pub struct Glyph {
/// the unicode code point of the glyph
pub glyph: char,
/// The visual position in which the glyph appears; i.e., the human-comprehensible location
/// of the glyph in the source text
pub position: Position,
/// The byte offsets corresponding to this topoglyph in the source data; i.e., the
/// machine-comprehensible location of the glyph in the source text
pub bytes: Range<u64>,
}
impl Glyph {
/// checks to see whether or not the character for this glyph is a word character
pub fn is_word(&self) -> bool {
match self.glyph {
'-' | '/' | '\\' | '.' | ':' | '~' => true,
_ => self.glyph.is_alphanumeric(),
}
}
/// checks to see whether or not the character for this glyph is a glob character
pub fn is_glob(&self) -> bool {
self.is_word() || self.glyph == '*'
}
}
impl fmt::Debug for Glyph {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
f,
"[{char} ({pos:?})]",
char = self.glyph,
pos = self.position
)
}
}
/// Glyphs produces [glyphs](Glyph) for a source text; i.e., it is an iterator of [Glyph] values.
/// Glyphs is used to control reading from the source text and keeps a lookahead buffer of glyphs
/// that have not been processed. While a [crate::lex::Lexer] is responsible for the creation and
/// iteration of [tokens](crate::lex::Token), Glyphs is responsible for the creation and iteration
/// of glyphs.
pub struct Glyphs<'text> {
/// the characters of the input text that we're interested in reading
source: Chars<'text>,
/// the position of the next character in the source text
pub(crate) next_position: Position,
/// the number of bytes we've read from our input document so far
bytes_read: u64,
/// lookahead buffer
lookahead: VecDeque<Glyph>,
}
impl<'text> Glyphs<'text> {
pub fn new(source: &'text str) -> Self {
// neat
Self {
source: source.chars(),
next_position: Position::origin(),
bytes_read: 0,
lookahead: VecDeque::new(),
}
}
/// reads the next n characters from the source text into our lookahead buffer
fn fill_lookahead(&mut self, n: usize) -> bool {
while self.lookahead.len() < n {
let c = match self.source.next() {
Some(c) => c,
None => break,
};
let len = c.len_utf8();
let start = self.bytes_read;
self.bytes_read += len as u64;
let position = if c == '\n' {
self.next_position.incr_line()
} else {
self.next_position.incr()
};
self.lookahead.push_back(Glyph {
glyph: c,
position,
bytes: Range {
start,
end: self.bytes_read,
},
})
}
self.lookahead.len() == n
}
/// returns a reference to the next character from the source text, advancing our internal
/// lookahead buffer if necessary. Returns None if we're already at the end of our source text.
pub fn peek(&mut self) -> Option<&Glyph> {
self.peek_at(0)
}
/// takes the next character from our input text
pub fn pop(&mut self) -> Result<Glyph, LexError> {
self.next()
.ok_or(LexError::UnexpectedEOF(self.next_position))
}
/// returns a reference to a character in our lookahead buffer at a given position. This allows
/// us to perform a lookahead read without consuming any tokens, maintaining our current
/// position and keeping our unconsumed characters safe.
fn peek_at(&mut self, idx: usize) -> Option<&Glyph> {
self.fill_lookahead(idx + 1);
self.lookahead.get(idx)
}
/// discards characters from our current position so long as the upcoming characters match some
/// predicate. This is called yeet_while instead of skip_while in order to avoid conflicting
/// with the
/// [skip_while](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.skip_while)
/// method of the stdlib Iterator trait.
pub fn yeet_while<F>(&mut self, mut pred: F)
where
F: FnMut(&Glyph) -> bool,
{
while let Some(g) = self.peek() {
if pred(&g) {
self.next();
} else {
return;
}
}
}
/// discards all of the upcoming whitespace characters. After calling yeet_whitespace, the next
/// Glyp is guaranteed to be either non-whitespace or None (None would be because we've hit
/// EOF)
pub fn yeet_whitespace(&mut self) {
self.yeet_while(|tg| tg.glyph.is_whitespace());
}
}
impl<'text> Iterator for Glyphs<'text> {
type Item = Glyph;
fn next(&mut self) -> Option<Self::Item> {
self.fill_lookahead(1);
self.lookahead.pop_front()
}
}
Loading…
Cancel
Save