diff --git a/Cargo.lock b/Cargo.lock index 7157511a..e2d8f403 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,6 +78,7 @@ dependencies = [ "internment", "miette", "pretty_assertions", + "thiserror", "vec1", ] diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs index 0bc9b165..e56b64b1 100644 --- a/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -1,18 +1,80 @@ -use std::{io, path::PathBuf}; +use std::{ + fmt::{Debug, Display}, + io, + path::PathBuf, +}; use aiken_lang::error::ParseError; +use miette::{EyreContext, LabeledSpan, MietteHandlerOpts, RgbColors, SourceCode}; #[allow(dead_code)] -#[derive(Debug, thiserror::Error, miette::Diagnostic)] +#[derive(thiserror::Error)] pub enum Error { #[error("file operation failed")] FileIo { path: PathBuf, error: io::Error }, - #[error("failed to parse Aiken source code")] + #[error("failed to parse")] Parse { path: PathBuf, + src: String, + + #[source] error: Box, }, - #[error("list of errors")] + /// Useful for returning many [`Error::Parse`] at once + #[error("a list of errors")] List(Vec), } + +impl Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let miette_handler = MietteHandlerOpts::new() + // For better support of terminal themes use the ANSI coloring + .rgb_colors(RgbColors::Never) + // If ansi support is disabled in the config disable the eye-candy + .color(true) + .unicode(true) + .terminal_links(true) + .build(); + + // Ignore error to prevent format! panics. This can happen if span points at some + // inaccessible location, for example by calling `report_error()` with wrong working set. + let _ = miette_handler.debug(self, f); + + Ok(()) + } +} + +impl miette::Diagnostic for Error { + fn code<'a>(&'a self) -> Option> { + match self { + Error::Parse { .. } => Some(Box::new("aiken::parser".to_string())), + Error::FileIo { .. } => None, + Error::List(_) => None, + } + } + + fn source_code(&self) -> Option<&dyn SourceCode> { + match self { + Error::Parse { src, .. } => Some(src), + Error::FileIo { .. } => None, + Error::List(_) => None, + } + } + + fn labels(&self) -> Option + '_>> { + match self { + Error::Parse { error, .. } => error.labels(), + Error::FileIo { .. } => None, + Error::List(_) => None, + } + } + + fn help<'a>(&'a self) -> Option> { + match self { + Error::Parse { error, .. } => error.kind.help(), + Error::FileIo { .. } => None, + Error::List(_) => None, + } + } +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index a60b4e56..5af2c5ec 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -16,13 +16,15 @@ use uplc::{ }, }; -use aiken::{config::Config, project::Project}; +use aiken::{config::Config, error, project::Project}; mod args; use args::{Args, TxCommand, UplcCommand}; fn main() -> miette::Result<()> { + miette::set_panic_hook(); + let args = Args::default(); match args { @@ -47,7 +49,16 @@ fn main() -> miette::Result<()> { let mut project = Project::new(config, project_path); - project.build()?; + if let Err(err) = project.build() { + match err { + error::Error::List(errors) => { + for error in errors { + eprintln!("Error: {:?}", error) + } + } + rest => Err(rest)?, + } + }; } Args::Dev => { diff --git a/crates/cli/src/project.rs b/crates/cli/src/project.rs index bb792c3b..059f3e43 100644 --- a/crates/cli/src/project.rs +++ b/crates/cli/src/project.rs @@ -17,6 +17,7 @@ pub struct Source { } #[derive(Debug)] +#[allow(dead_code)] struct ParsedModule { path: PathBuf, name: String, diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml index 62c2db56..c54320c7 100644 --- a/crates/lang/Cargo.toml +++ b/crates/lang/Cargo.toml @@ -14,6 +14,7 @@ authors = ["Lucas Rosa ", "Kasey White "] chumsky = "0.8.0" internment = "0.7.0" miette = "5.2.0" +thiserror = "1.0.37" vec1 = "1.8.0" [dev-dependencies] diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index b9408d8a..99cd267b 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -498,6 +498,12 @@ pub struct Span { pub end: usize, } +impl From for miette::SourceSpan { + fn from(span: Span) -> Self { + Self::new(span.start.into(), span.end.into()) + } +} + impl Span { pub fn empty() -> Self { use chumsky::Span; diff --git a/crates/lang/src/error.rs b/crates/lang/src/error.rs index ecfce194..f860f97f 100644 --- a/crates/lang/src/error.rs +++ b/crates/lang/src/error.rs @@ -1,11 +1,15 @@ use std::{collections::HashSet, fmt}; +use miette::Diagnostic; + use crate::{ast::Span, token::Token}; -#[derive(Debug)] +#[derive(Debug, Diagnostic, thiserror::Error)] +#[error("{}", .kind)] pub struct ParseError { - kind: ErrorKind, - span: Span, + pub kind: ErrorKind, + #[label] + pub span: Span, #[allow(dead_code)] while_parsing: Option<(Span, &'static str)>, expected: HashSet, @@ -63,19 +67,24 @@ impl> chumsky::Error for ParseError { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Diagnostic, thiserror::Error)] pub enum ErrorKind { + #[error("unexpected end")] UnexpectedEnd, + #[error("unexpected {0}")] + #[diagnostic(help("try removing it"))] Unexpected(Pattern), + #[error("unclosed {start}")] Unclosed { start: Pattern, before_span: Span, before: Option, }, + #[error("no end branch")] NoEndBranch, } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)] pub enum Pattern { Char(char), Token(Token), diff --git a/examples/sample/scripts/swap.ak b/examples/sample/scripts/swap.ak index b921bee5..73de6df7 100644 --- a/examples/sample/scripts/swap.ak +++ b/examples/sample/scripts/swap.ak @@ -5,7 +5,7 @@ pub type Datum { } pub type Redeemer { - Buy + Buy, Sell }