diff --git a/Cargo.toml b/Cargo.toml index d52ad06..818d5e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ thiserror = "1.0" macros = { path = "macros" } dirs = "4" -log = { version = "0.4", features = [ "max_level_off", "release_max_level_off" ] } +log = { version = "0.4", features = [ "max_level_debug", "release_max_level_off" ] } [dependencies.windows] version = "0.44.0" diff --git a/src/error.rs b/src/error.rs index fa22456..8347112 100644 --- a/src/error.rs +++ b/src/error.rs @@ -65,11 +65,20 @@ pub enum ParseError { #[derive(Debug, Error)] pub enum ExecError { - #[error("failed waiting on child process")] - ProcessWaitError(#[source] io::Error), - - #[error("failed to spawn child process")] - ProcessSpawnError(#[source] io::Error), + #[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("value type error: {0}")] ValueTypeError(String), diff --git a/src/lex.rs b/src/lex.rs index f270261..1db4fd5 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -9,15 +9,16 @@ pub fn lex(source: &str) -> Result, LexError> { Lexer::new(source).collect() } -/// A Lexeme is the text of a given Token, without respect to that Token's type, but with respect -/// to where the text appears relative to some source code. This is, simply, a string that contains -/// the addresses of each of its characters with respect to some source text. +/// 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, } 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> { if self.elems.is_empty() { return None; @@ -75,6 +76,10 @@ pub enum Token { } 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::*; @@ -86,17 +91,20 @@ impl Token { } } +/// Tokenizer splits some input [Glyphs] into [Token] values pub 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> { self.source.yeet_whitespace(); let next = self.source.next()?; @@ -235,7 +243,7 @@ mod tests { .chars() .map(|c| Glyph { glyph: c, - position: Position::start(), + position: Position::origin(), bytes: 0..0, }) .collect(); diff --git a/src/main.rs b/src/main.rs index fac1718..82e3c94 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,38 @@ +/// hi + +/// builtin functions mod builtins; + +/// all of the errors for the clyde project live in this module mod error; + mod ext; + +/// handles input from terminals mod input; + +/// key presses, independent from input sources mod key; + +/// lexical analysis mod lex; + +/// a real primitive line editor mod line; + mod log; mod output; + +/// turns our tokens into parse trees mod parse; + mod prompt; mod shell; + +/// syntax and semantic analysis mod syntax; + +/// topoglyph is a real word, i promise mod topo; use crate::log::*; @@ -68,12 +90,12 @@ fn main() -> Result<()> { let mut state = syntax::State::new(); if let Err(e) = tree.eval(&mut state) { error!("{e:?}"); - println!("Error: {e:?}"); + shell.render_error(e); } } Err(e) => { error!("{e:?}"); - println!("Error: {e:?}"); + shell.render_error(e); } } // shell.exec(tree.into())?; @@ -202,7 +224,3 @@ fn main() -> Result<()> { } } } - -/* - -*/ diff --git a/src/output.rs b/src/output.rs index ceddd6d..7e7276a 100644 --- a/src/output.rs +++ b/src/output.rs @@ -103,7 +103,7 @@ 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) } @@ -146,11 +146,6 @@ impl Writer { 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 { diff --git a/src/shell.rs b/src/shell.rs index b2c0695..6ae420a 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -6,7 +6,12 @@ use crate::{ output, syntax, }; -use std::path::{Path, PathBuf}; +use std::{ + error::Error, + fmt, + io::{self, Write}, + path::{Path, PathBuf}, +}; use anyhow::Result; use dirs; @@ -153,4 +158,21 @@ impl Shell { } } } + + pub fn render_error(&mut self, e: E) -> io::Result<()> { + self.render_error_helper(e, 0)?; + Ok(()) + } + + fn render_error_helper(&mut self, e: E, depth: u8) -> io::Result<()> { + if depth > 0 { + println!(" {e}"); + } else { + println!("{e}:"); + } + if let Some(cause) = e.source() { + self.render_error_helper(cause, depth + 1)?; + } + Ok(()) + } } diff --git a/src/syntax.rs b/src/syntax.rs index da1fad1..705e844 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -103,7 +103,7 @@ pub struct Command { impl Eval for Command { fn eval(&self, ctx: &mut State) -> Result { let name = self.name.eval(ctx)?.try_to_string()?; - let mut proc = process::Command::new(name); + let mut proc = process::Command::new(&name); if self.args.len() > 0 { let args = self @@ -113,8 +113,16 @@ impl Eval for Command { .collect::, ExecError>>()?; proc.args(args); } - let mut child = proc.spawn().map_err(|e| ExecError::ProcessSpawnError(e))?; - let status = child.wait().map_err(|e| ExecError::ProcessWaitError(e))?; + let mut child = proc.spawn().map_err(|e| ExecError::ProcessSpawnError { + name: name.clone(), + source: e, + })?; + let pid = child.id(); + let status = child.wait().map_err(|e| ExecError::ProcessWaitError { + name, + pid, + source: e, + })?; return Ok(Value::ExitStatus(status)); } } diff --git a/src/topo.rs b/src/topo.rs index 316acf7..3471077 100644 --- a/src/topo.rs +++ b/src/topo.rs @@ -1,9 +1,9 @@ use crate::error::LexError; use std::{collections::VecDeque, fmt, ops::Range, str::Chars}; -/// The position of a specific glyph within a corpus of text. We use this for rendering error -/// messages and communicating to the user the location of errors. -#[derive(Debug, PartialEq, Clone, Copy)] +/// 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, @@ -13,7 +13,8 @@ pub struct Position { } impl Position { - pub fn start() -> Self { + /// 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 } } @@ -35,6 +36,18 @@ impl Position { } } +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 + ) + } +} + +/// 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 @@ -50,10 +63,12 @@ pub struct Glyph { } impl Glyph { + /// checks to see whether or not the character for this glyph is a word character pub fn is_word(&self) -> bool { self.glyph.is_alphanumeric() || self.glyph == '.' } + /// 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 == '*' } @@ -75,9 +90,16 @@ impl fmt::Debug for Glyph { /// 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 next_position: Position, + + /// the number of bytes we've read from our input document so far bytes_read: u64, + + /// lookahead buffer lookahead: VecDeque, } @@ -86,7 +108,7 @@ impl<'text> Glyphs<'text> { // neat Self { source: source.chars(), - next_position: Position::start(), + next_position: Position::origin(), bytes_read: 0, lookahead: VecDeque::new(), } @@ -157,6 +179,9 @@ impl<'text> Glyphs<'text> { } } + /// 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()); }