error printing is less bad

main
Jordan Orelli 9 months ago
parent 5108e4457f
commit 27ebbf7c97

@ -10,7 +10,7 @@ thiserror = "1.0"
macros = { path = "macros" } macros = { path = "macros" }
dirs = "4" 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] [dependencies.windows]
version = "0.44.0" version = "0.44.0"

@ -65,11 +65,20 @@ pub enum ParseError {
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ExecError { pub enum ExecError {
#[error("failed waiting on child process")] #[error("failed waiting on child process '{name}' ({pid})")]
ProcessWaitError(#[source] io::Error), ProcessWaitError {
name: String,
#[error("failed to spawn child process")] pid: u32,
ProcessSpawnError(#[source] io::Error), #[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}")] #[error("value type error: {0}")]
ValueTypeError(String), ValueTypeError(String),

@ -9,15 +9,16 @@ pub fn lex(source: &str) -> Result<Vec<Token>, LexError> {
Lexer::new(source).collect() Lexer::new(source).collect()
} }
/// A Lexeme is the text of a given Token, without respect to that Token's type, but with respect /// A Lexeme is the text of a given [Token]. The lexeme contains no information about the Token's
/// to where the text appears relative to some source code. This is, simply, a string that contains /// meaning or type; it is just a fancy string with position information.
/// the addresses of each of its characters with respect to some source text.
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub struct Lexeme { pub struct Lexeme {
elems: Vec<Glyph>, elems: Vec<Glyph>,
} }
impl Lexeme { 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>> { fn span(&self) -> Option<Range<Position>> {
if self.elems.is_empty() { if self.elems.is_empty() {
return None; return None;
@ -75,6 +76,10 @@ pub enum Token {
} }
impl 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 { fn same(&self, other: &Self) -> bool {
use Token::*; use Token::*;
@ -86,17 +91,20 @@ impl Token {
} }
} }
/// Tokenizer splits some input [Glyphs] into [Token] values
pub struct Tokenizer<'text> { pub struct Tokenizer<'text> {
source: Glyphs<'text>, source: Glyphs<'text>,
} }
impl<'text> Tokenizer<'text> { impl<'text> Tokenizer<'text> {
/// creates a new tokenizer for some given input source text
pub fn new(text: &'text str) -> Self { pub fn new(text: &'text str) -> Self {
Self { Self {
source: Glyphs::new(text), 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>> { fn next_token(&mut self) -> Option<Result<Token, LexError>> {
self.source.yeet_whitespace(); self.source.yeet_whitespace();
let next = self.source.next()?; let next = self.source.next()?;
@ -235,7 +243,7 @@ mod tests {
.chars() .chars()
.map(|c| Glyph { .map(|c| Glyph {
glyph: c, glyph: c,
position: Position::start(), position: Position::origin(),
bytes: 0..0, bytes: 0..0,
}) })
.collect(); .collect();

@ -1,16 +1,38 @@
/// hi
/// builtin functions
mod builtins; mod builtins;
/// all of the errors for the clyde project live in this module
mod error; mod error;
mod ext; mod ext;
/// handles input from terminals
mod input; mod input;
/// key presses, independent from input sources
mod key; mod key;
/// lexical analysis
mod lex; mod lex;
/// a real primitive line editor
mod line; mod line;
mod log; mod log;
mod output; mod output;
/// turns our tokens into parse trees
mod parse; mod parse;
mod prompt; mod prompt;
mod shell; mod shell;
/// syntax and semantic analysis
mod syntax; mod syntax;
/// topoglyph is a real word, i promise
mod topo; mod topo;
use crate::log::*; use crate::log::*;
@ -68,12 +90,12 @@ fn main() -> Result<()> {
let mut state = syntax::State::new(); let mut state = syntax::State::new();
if let Err(e) = tree.eval(&mut state) { if let Err(e) = tree.eval(&mut state) {
error!("{e:?}"); error!("{e:?}");
println!("Error: {e:?}"); shell.render_error(e);
} }
} }
Err(e) => { Err(e) => {
error!("{e:?}"); error!("{e:?}");
println!("Error: {e:?}"); shell.render_error(e);
} }
} }
// shell.exec(tree.into())?; // shell.exec(tree.into())?;
@ -202,7 +224,3 @@ fn main() -> Result<()> {
} }
} }
} }
/*
*/

@ -103,7 +103,7 @@ impl Writer {
unsafe { unsafe {
let handle = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE) let handle = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE)
.context("unable to get stdout handle")?; .context("unable to get stdout handle")?;
let mut stdout = Self{output: handle}; let mut stdout = Self { output: handle };
stdout.reset()?; stdout.reset()?;
Ok(stdout) Ok(stdout)
} }
@ -146,11 +146,6 @@ impl Writer {
self.write(b"\x1b[2J\x1b[0;0H")?; self.write(b"\x1b[2J\x1b[0;0H")?;
Ok(()) Ok(())
} }
// pub fn hide_cursor(&mut self) -> Result<()> {
// self.write(b"\x1b[?25l")?;
// Ok(())
// }
} }
impl Write for Writer { impl Write for Writer {

@ -6,7 +6,12 @@ use crate::{
output, syntax, output, syntax,
}; };
use std::path::{Path, PathBuf}; use std::{
error::Error,
fmt,
io::{self, Write},
path::{Path, PathBuf},
};
use anyhow::Result; use anyhow::Result;
use dirs; use dirs;
@ -153,4 +158,21 @@ impl Shell {
} }
} }
} }
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 {
println!(" {e}");
} else {
println!("{e}:");
}
if let Some(cause) = e.source() {
self.render_error_helper(cause, depth + 1)?;
}
Ok(())
}
} }

@ -103,7 +103,7 @@ pub struct Command {
impl Eval for Command { impl Eval for Command {
fn eval(&self, ctx: &mut State) -> Result<Value, ExecError> { fn eval(&self, ctx: &mut State) -> Result<Value, ExecError> {
let name = self.name.eval(ctx)?.try_to_string()?; 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 { if self.args.len() > 0 {
let args = self let args = self
@ -113,8 +113,16 @@ impl Eval for Command {
.collect::<Result<Vec<String>, ExecError>>()?; .collect::<Result<Vec<String>, ExecError>>()?;
proc.args(args); proc.args(args);
} }
let mut child = proc.spawn().map_err(|e| ExecError::ProcessSpawnError(e))?; let mut child = proc.spawn().map_err(|e| ExecError::ProcessSpawnError {
let status = child.wait().map_err(|e| ExecError::ProcessWaitError(e))?; 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)); return Ok(Value::ExitStatus(status));
} }
} }

@ -1,9 +1,9 @@
use crate::error::LexError; use crate::error::LexError;
use std::{collections::VecDeque, fmt, ops::Range, str::Chars}; 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 /// A position a some two dimensional space defined by lines and columns, specifically concerned
/// messages and communicating to the user the location of errors. /// with positions of characters within text
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(PartialEq, Clone, Copy)]
pub struct Position { pub struct Position {
/// The visual line in which this glyph appears in the source text /// The visual line in which this glyph appears in the source text
pub line: u64, pub line: u64,
@ -13,7 +13,8 @@ pub struct Position {
} }
impl 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 } 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)] #[derive(PartialEq, Clone)]
pub struct Glyph { pub struct Glyph {
/// the unicode code point of the glyph /// the unicode code point of the glyph
@ -50,10 +63,12 @@ pub struct Glyph {
} }
impl Glyph { impl Glyph {
/// checks to see whether or not the character for this glyph is a word character
pub fn is_word(&self) -> bool { pub fn is_word(&self) -> bool {
self.glyph.is_alphanumeric() || self.glyph == '.' 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 { pub fn is_glob(&self) -> bool {
self.is_word() || self.glyph == '*' 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 /// iteration of [tokens](crate::lex::Token), Glyphs is responsible for the creation and iteration
/// of glyphs. /// of glyphs.
pub struct Glyphs<'text> { pub struct Glyphs<'text> {
/// the characters of the input text that we're interested in reading
source: Chars<'text>, source: Chars<'text>,
/// the position of the next character in the source text
next_position: Position, next_position: Position,
/// the number of bytes we've read from our input document so far
bytes_read: u64, bytes_read: u64,
/// lookahead buffer
lookahead: VecDeque<Glyph>, lookahead: VecDeque<Glyph>,
} }
@ -86,7 +108,7 @@ impl<'text> Glyphs<'text> {
// neat // neat
Self { Self {
source: source.chars(), source: source.chars(),
next_position: Position::start(), next_position: Position::origin(),
bytes_read: 0, bytes_read: 0,
lookahead: VecDeque::new(), 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) { pub fn yeet_whitespace(&mut self) {
self.yeet_while(|tg| tg.glyph.is_whitespace()); self.yeet_while(|tg| tg.glyph.is_whitespace());
} }

Loading…
Cancel
Save