improve error display
This commit is contained in:
parent
6f3c152e73
commit
2bc8281f19
|
@ -1,21 +1,24 @@
|
||||||
/// An error that occurred during compilation.
|
/// An error that occurred during compilation.
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
|
#[derive(Debug, thiserror::Error, Clone, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("An error occurred while working with Input/Output: {0}")]
|
#[error("FileProviderError: {0}")]
|
||||||
IoError(String),
|
FileProviderError(#[from] super::FileProviderError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Utf8Error(#[from] std::str::Utf8Error),
|
|
||||||
#[error("An error occurred while lexing the source code: {0}")]
|
|
||||||
LexicalError(#[from] crate::lexical::Error),
|
LexicalError(#[from] crate::lexical::Error),
|
||||||
#[error("An error occured while tokenizing the source code: {0}")]
|
|
||||||
TokenizeError(#[from] crate::lexical::token::TokenizeError),
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ParseError(#[from] crate::syntax::error::Error),
|
ParseError(#[from] crate::syntax::error::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
TranspileError(#[from] crate::transpile::TranspileError),
|
TranspileError(#[from] crate::transpile::TranspileError),
|
||||||
#[error("An error occurred: {0}")]
|
#[error("An error occurred: {0}")]
|
||||||
Other(&'static str),
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
/// Creates a new error from a string.
|
||||||
|
pub fn other<S: Into<String>>(error: S) -> Self {
|
||||||
|
Self::Other(error.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A specialized [`Result`] type for this crate.
|
/// A specialized [`Result`] type for this crate.
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
fmt::Display,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Error;
|
|
||||||
|
|
||||||
/// A trait for providing file contents.
|
/// A trait for providing file contents.
|
||||||
pub trait FileProvider {
|
pub trait FileProvider {
|
||||||
/// Reads the contents of the file at the given path as bytes.
|
/// Reads the contents of the file at the given path as bytes.
|
||||||
|
@ -22,7 +22,12 @@ pub trait FileProvider {
|
||||||
/// - If the file is not valid UTF-8.
|
/// - If the file is not valid UTF-8.
|
||||||
fn read_str<P: AsRef<Path>>(&self, path: P) -> Result<Cow<str>, Error> {
|
fn read_str<P: AsRef<Path>>(&self, path: P) -> Result<Cow<str>, Error> {
|
||||||
let bytes = self.read_bytes(path)?;
|
let bytes = self.read_bytes(path)?;
|
||||||
let string = std::str::from_utf8(&bytes)?.to_string();
|
let string = std::str::from_utf8(&bytes)
|
||||||
|
.map_err(|err| {
|
||||||
|
let arc: Arc<dyn std::error::Error + Send + Sync> = Arc::new(err);
|
||||||
|
Error::other(arc)
|
||||||
|
})?
|
||||||
|
.to_string();
|
||||||
Ok(Cow::Owned(string))
|
Ok(Cow::Owned(string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,20 +61,118 @@ impl FileProvider for FsProvider {
|
||||||
let full_path = self.root.join(path);
|
let full_path = self.root.join(path);
|
||||||
std::fs::read(full_path)
|
std::fs::read(full_path)
|
||||||
.map(Cow::Owned)
|
.map(Cow::Owned)
|
||||||
.map_err(|err| Error::IoError(err.to_string()))
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_str<P: AsRef<Path>>(&self, path: P) -> Result<Cow<str>, Error> {
|
fn read_str<P: AsRef<Path>>(&self, path: P) -> Result<Cow<str>, Error> {
|
||||||
let full_path = self.root.join(path);
|
let full_path = self.root.join(path);
|
||||||
std::fs::read_to_string(full_path)
|
std::fs::read_to_string(full_path)
|
||||||
.map(Cow::Owned)
|
.map(Cow::Owned)
|
||||||
.map_err(|err| Error::IoError(err.to_string()))
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error type for [`FileProvider`] operations.
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
#[derive(Debug, Clone, thiserror::Error)]
|
||||||
|
pub struct Error {
|
||||||
|
kind: std::io::ErrorKind,
|
||||||
|
#[source]
|
||||||
|
error: Option<Arc<dyn std::error::Error + Send + Sync>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
/// Creates a new [`FileProviderError`] from a known kind of error as well as an
|
||||||
|
/// arbitrary error payload.
|
||||||
|
///
|
||||||
|
/// The `error` argument is an arbitrary
|
||||||
|
/// payload which will be contained in this [`FileProviderError`].
|
||||||
|
///
|
||||||
|
/// Note that this function allocates memory on the heap.
|
||||||
|
/// If no extra payload is required, use the `From` conversion from
|
||||||
|
/// `ErrorKind`.
|
||||||
|
pub fn new<E>(kind: std::io::ErrorKind, error: E) -> Self
|
||||||
|
where
|
||||||
|
E: Into<Arc<dyn std::error::Error + Send + Sync>>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
error: Some(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`FileProviderError`] from an arbitrary error payload.
|
||||||
|
///
|
||||||
|
/// It is a shortcut for [`FileProviderError::new`]
|
||||||
|
/// with [`std::io::ErrorKind::Other`].
|
||||||
|
pub fn other<E>(error: E) -> Self
|
||||||
|
where
|
||||||
|
E: Into<Arc<dyn std::error::Error + Send + Sync>>,
|
||||||
|
{
|
||||||
|
Self::new(std::io::ErrorKind::Other, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the inner error wrapped by this error (if any).
|
||||||
|
///
|
||||||
|
/// If this [`FileProviderError`] was constructed via [`Self::new`] then this function will
|
||||||
|
/// return [`Some`], otherwise it will return [`None`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_ref(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
|
||||||
|
return self.error.as_deref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the [`FileProviderError`], returning its inner error (if any).
|
||||||
|
///
|
||||||
|
/// If this [`Error`] was constructed via [`Self::new`] then this function will
|
||||||
|
/// return [`Some`], otherwise it will return [`None`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_inner(self) -> Option<Arc<dyn std::error::Error + Send + Sync>> {
|
||||||
|
self.error
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the corresponding [`std::io::ErrorKind`] for this error.
|
||||||
|
#[must_use]
|
||||||
|
pub fn kind(&self) -> std::io::ErrorKind {
|
||||||
|
self.kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match &self.error {
|
||||||
|
Some(err) => write!(f, "{}: {}", self.kind, err),
|
||||||
|
None => write!(f, "{}", self.kind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Error {
|
||||||
|
fn eq(&self, _: &Self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::ErrorKind> for Error {
|
||||||
|
fn from(value: std::io::ErrorKind) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: value,
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(value: std::io::Error) -> Self {
|
||||||
|
let kind = value.kind();
|
||||||
|
let error = value.into_inner().map(Arc::from);
|
||||||
|
|
||||||
|
Self { kind, error }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "shulkerbox")]
|
#[cfg(feature = "shulkerbox")]
|
||||||
mod vfs {
|
mod vfs {
|
||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
use super::{Error, FileProvider, Path};
|
use super::{Error, FileProvider, Path};
|
||||||
use shulkerbox::virtual_fs::{VFile, VFolder};
|
use shulkerbox::virtual_fs::{VFile, VFolder};
|
||||||
|
@ -77,10 +180,10 @@ mod vfs {
|
||||||
impl FileProvider for VFolder {
|
impl FileProvider for VFolder {
|
||||||
fn read_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Cow<[u8]>, Error> {
|
fn read_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Cow<[u8]>, Error> {
|
||||||
normalize_path_str(path).map_or_else(
|
normalize_path_str(path).map_or_else(
|
||||||
|| Err(Error::IoError("Invalid path".to_string())),
|
|| Err(Error::from(std::io::ErrorKind::InvalidData)),
|
||||||
|path| {
|
|path| {
|
||||||
self.get_file(&path)
|
self.get_file(&path)
|
||||||
.ok_or_else(|| Error::IoError("File not found".to_string()))
|
.ok_or_else(|| Error::from(std::io::ErrorKind::NotFound))
|
||||||
.map(|file| Cow::Borrowed(file.as_bytes()))
|
.map(|file| Cow::Borrowed(file.as_bytes()))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -88,15 +191,18 @@ mod vfs {
|
||||||
|
|
||||||
fn read_str<P: AsRef<Path>>(&self, path: P) -> Result<Cow<str>, Error> {
|
fn read_str<P: AsRef<Path>>(&self, path: P) -> Result<Cow<str>, Error> {
|
||||||
normalize_path_str(path).map_or_else(
|
normalize_path_str(path).map_or_else(
|
||||||
|| Err(Error::IoError("Invalid path".to_string())),
|
|| Err(Error::from(std::io::ErrorKind::InvalidData)),
|
||||||
|path| {
|
|path| {
|
||||||
self.get_file(&path)
|
self.get_file(&path)
|
||||||
.ok_or_else(|| Error::IoError("File not found".to_string()))
|
.ok_or_else(|| Error::from(std::io::ErrorKind::NotFound))
|
||||||
.and_then(|file| match file {
|
.and_then(|file| match file {
|
||||||
VFile::Text(text) => Ok(Cow::Borrowed(text.as_str())),
|
VFile::Text(text) => Ok(Cow::Borrowed(text.as_str())),
|
||||||
VFile::Binary(bin) => {
|
VFile::Binary(bin) => {
|
||||||
let string = std::str::from_utf8(bin)
|
let string = std::str::from_utf8(bin).map_err(|err| {
|
||||||
.map_err(|err| Error::IoError(err.to_string()))?;
|
let arc: Arc<dyn std::error::Error + Send + Sync> =
|
||||||
|
Arc::new(err);
|
||||||
|
Error::new(std::io::ErrorKind::InvalidData, arc)
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(Cow::Borrowed(string))
|
Ok(Cow::Borrowed(string))
|
||||||
}
|
}
|
||||||
|
@ -161,10 +267,7 @@ mod vfs {
|
||||||
dir.read_str("bar/baz.txt").unwrap().into_owned(),
|
dir.read_str("bar/baz.txt").unwrap().into_owned(),
|
||||||
"bar, baz".to_string()
|
"bar, baz".to_string()
|
||||||
);
|
);
|
||||||
assert!(matches!(
|
assert!(dir.read_str("nonexistent.txt").is_err());
|
||||||
dir.read_str("nonexistent.txt"),
|
|
||||||
Err(Error::IoError(_))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@ mod diagnostic;
|
||||||
pub use diagnostic::{Handler, PrintHandler, SilentHandler, VoidHandler};
|
pub use diagnostic::{Handler, PrintHandler, SilentHandler, VoidHandler};
|
||||||
|
|
||||||
mod file_provider;
|
mod file_provider;
|
||||||
pub use file_provider::{FileProvider, FsProvider};
|
pub use file_provider::{Error as FileProviderError, FileProvider, FsProvider};
|
||||||
|
|
||||||
pub mod log;
|
pub mod log;
|
||||||
|
|
|
@ -7,16 +7,18 @@ use crate::base::{
|
||||||
source_file::Span,
|
source_file::Span,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::token_stream::Delimiter;
|
use super::{token, token_stream::Delimiter};
|
||||||
|
|
||||||
/// Represents an error that occurred during the lexical analysis of the source code.
|
/// Represents an error that occurred during the lexical analysis of the source code.
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, thiserror::Error)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Comment is not terminated.")]
|
#[error(transparent)]
|
||||||
UnterminatedDelimitedComment(#[from] UnterminatedDelimitedComment),
|
UnterminatedDelimitedComment(#[from] UnterminatedDelimitedComment),
|
||||||
#[error("Delimiter is not terminated.")]
|
#[error(transparent)]
|
||||||
UndelimitedDelimiter(#[from] UndelimitedDelimiter),
|
UndelimitedDelimiter(#[from] UndelimitedDelimiter),
|
||||||
|
#[error("Tokenize error: {0}")]
|
||||||
|
TokenizeError(#[from] token::TokenizeError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Source code contains an unclosed `/*` comment.
|
/// Source code contains an unclosed `/*` comment.
|
||||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -81,7 +81,7 @@ pub fn parse(
|
||||||
let tokens = tokenize(handler, file_provider, path, identifier)?;
|
let tokens = tokenize(handler, file_provider, path, identifier)?;
|
||||||
|
|
||||||
if handler.has_received() {
|
if handler.has_received() {
|
||||||
return Err(Error::Other(
|
return Err(Error::other(
|
||||||
"An error occurred while tokenizing the source code.",
|
"An error occurred while tokenizing the source code.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -89,12 +89,12 @@ pub fn parse(
|
||||||
tracing::info!("Parsing the source code at path: {}", path.display());
|
tracing::info!("Parsing the source code at path: {}", path.display());
|
||||||
|
|
||||||
let mut parser = Parser::new(&tokens);
|
let mut parser = Parser::new(&tokens);
|
||||||
let program = parser.parse_program(handler).ok_or(Error::Other(
|
let program = parser
|
||||||
"An error occurred while parsing the source code.",
|
.parse_program(handler)
|
||||||
))?;
|
.ok_or_else(|| Error::other("An error occurred while parsing the source code."))?;
|
||||||
|
|
||||||
if handler.has_received() {
|
if handler.has_received() {
|
||||||
return Err(Error::Other(
|
return Err(Error::other(
|
||||||
"An error occurred while parsing the source code.",
|
"An error occurred while parsing the source code.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ where
|
||||||
let datapack = transpiler.into_datapack();
|
let datapack = transpiler.into_datapack();
|
||||||
|
|
||||||
if handler.has_received() {
|
if handler.has_received() {
|
||||||
return Err(Error::Other(
|
return Err(Error::other(
|
||||||
"An error occurred while transpiling the source code.",
|
"An error occurred while transpiling the source code.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue