Use miette for reporting errors
This gives us errors with annotated context like this:
```
Error:
× type error: Unknown variable 'x'
╭─[isle_examples/let.isle:24:1]
24 │ (Lower (B.B z))
25 │ (A.Add x y))
· ┬
· ╰── Unknown variable 'x'
╰────
```
This commit is contained in:
committed by
Chris Fallin
parent
38da2cee3e
commit
6ffb02d9f6
@@ -7,4 +7,5 @@ license = "Apache-2.0 WITH LLVM-exception"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
miette = "3.0.0"
|
||||
thiserror = "1.0.29"
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use crate::lexer::Pos;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The parsed form of an ISLE file.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Defs {
|
||||
pub defs: Vec<Def>,
|
||||
pub filenames: Vec<String>,
|
||||
pub filenames: Vec<Arc<str>>,
|
||||
pub file_texts: Vec<Arc<str>>,
|
||||
}
|
||||
|
||||
/// One toplevel form in an ISLE file.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! Compilation process, from AST to Sema to Sequences of Insts.
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::error::Result;
|
||||
use crate::{ast, codegen, sema};
|
||||
|
||||
/// Compile the given AST definitions into Rust source code.
|
||||
pub fn compile(defs: &ast::Defs) -> Result<String, Vec<Error>> {
|
||||
pub fn compile(defs: &ast::Defs) -> Result<String> {
|
||||
let mut typeenv = sema::TypeEnv::from_ast(defs)?;
|
||||
let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?;
|
||||
Ok(codegen::codegen(&typeenv, &termenv))
|
||||
|
||||
@@ -1,26 +1,56 @@
|
||||
//! Error types.
|
||||
|
||||
use miette::{Diagnostic, SourceCode, SourceSpan};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::lexer::Pos;
|
||||
/// Either `Ok(T)` or `Err(isle::Error)`.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Errors produced by ISLE.
|
||||
#[derive(thiserror::Error, Clone, Debug)]
|
||||
#[derive(thiserror::Error, Diagnostic, Clone, Debug)]
|
||||
pub enum Error {
|
||||
/// An I/O error.
|
||||
#[error(transparent)]
|
||||
IoError(Arc<std::io::Error>),
|
||||
|
||||
/// The input ISLE source has an error.
|
||||
#[error("{}:{}:{}: {}", .filename, .pos.line, .pos.col, .msg)]
|
||||
CompileError {
|
||||
/// The input ISLE source has a parse error.
|
||||
#[error("parse error: {msg}")]
|
||||
#[diagnostic()]
|
||||
ParseError {
|
||||
/// The error message.
|
||||
msg: String,
|
||||
/// The ISLE source filename where the error occurs.
|
||||
filename: String,
|
||||
/// The position within the file that the error occurs at.
|
||||
pos: Pos,
|
||||
|
||||
/// The input ISLE source.
|
||||
#[source_code]
|
||||
src: Source,
|
||||
|
||||
/// The location of the parse error.
|
||||
#[label("{msg}")]
|
||||
span: SourceSpan,
|
||||
},
|
||||
|
||||
/// The input ISLE source has a type error.
|
||||
#[error("type error: {msg}")]
|
||||
#[diagnostic()]
|
||||
TypeError {
|
||||
/// The error message.
|
||||
msg: String,
|
||||
|
||||
/// The input ISLE source.
|
||||
#[source_code]
|
||||
src: Source,
|
||||
|
||||
/// The location of the type error.
|
||||
#[label("{msg}")]
|
||||
span: SourceSpan,
|
||||
},
|
||||
|
||||
/// Multiple errors.
|
||||
#[error("Found {} errors:\n\n{}",
|
||||
self.unwrap_errors().len(),
|
||||
DisplayErrors(self.unwrap_errors()))]
|
||||
#[diagnostic()]
|
||||
Errors(#[related] Vec<Error>),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
@@ -28,3 +58,81 @@ impl From<std::io::Error> for Error {
|
||||
Error::IoError(Arc::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Error>> for Error {
|
||||
fn from(es: Vec<Error>) -> Self {
|
||||
Error::Errors(es)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
fn unwrap_errors(&self) -> &[Error] {
|
||||
match self {
|
||||
Error::Errors(e) => e,
|
||||
_ => panic!("`isle::Error::unwrap_errors` on non-`isle::Error::Errors`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayErrors<'a>(&'a [Error]);
|
||||
impl std::fmt::Display for DisplayErrors<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for e in self.0 {
|
||||
writeln!(f, "{}", e)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A source file and its contents.
|
||||
#[derive(Clone)]
|
||||
pub struct Source {
|
||||
name: Arc<str>,
|
||||
text: Arc<str>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Source {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Source")
|
||||
.field("name", &self.name)
|
||||
.field("source", &"<redacted>");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Source {
|
||||
pub(crate) fn new(name: Arc<str>, text: Arc<str>) -> Self {
|
||||
Self { name, text }
|
||||
}
|
||||
|
||||
/// Get this source's file name.
|
||||
pub fn name(&self) -> &Arc<str> {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Get this source's text contents.
|
||||
pub fn text(&self) -> &Arc<str> {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceCode for Source {
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
span: &SourceSpan,
|
||||
context_lines_before: usize,
|
||||
context_lines_after: usize,
|
||||
) -> std::result::Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
|
||||
let contents = self
|
||||
.text
|
||||
.read_span(span, context_lines_before, context_lines_after)?;
|
||||
Ok(Box::new(miette::MietteSpanContents::new_named(
|
||||
self.name.to_string(),
|
||||
contents.data(),
|
||||
contents.span().clone(),
|
||||
contents.line(),
|
||||
contents.column(),
|
||||
contents.line_count(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! Lexer for the ISLE language.
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::error::Result;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The lexer.
|
||||
///
|
||||
@@ -11,7 +12,13 @@ pub struct Lexer<'a> {
|
||||
/// Arena of filenames from the input source.
|
||||
///
|
||||
/// Indexed via `Pos::file`.
|
||||
pub filenames: Vec<String>,
|
||||
pub filenames: Vec<Arc<str>>,
|
||||
|
||||
/// Arena of file source texts.
|
||||
///
|
||||
/// Indexed via `Pos::file`.
|
||||
pub file_texts: Vec<Arc<str>>,
|
||||
|
||||
file_starts: Vec<usize>,
|
||||
buf: Cow<'a, [u8]>,
|
||||
pos: Pos,
|
||||
@@ -36,11 +43,11 @@ pub struct Pos {
|
||||
|
||||
impl Pos {
|
||||
/// Print this source position as `file.isle:12:34`.
|
||||
pub fn pretty_print(&self, filenames: &[String]) -> String {
|
||||
pub fn pretty_print(&self, filenames: &[Arc<str>]) -> String {
|
||||
format!("{}:{}:{}", filenames[self.file], self.line, self.col)
|
||||
}
|
||||
/// Print this source position as `file.isle line 12`.
|
||||
pub fn pretty_print_line(&self, filenames: &[String]) -> String {
|
||||
pub fn pretty_print_line(&self, filenames: &[Arc<str>]) -> String {
|
||||
format!("{} line {}", filenames[self.file], self.line)
|
||||
}
|
||||
}
|
||||
@@ -66,7 +73,8 @@ impl<'a> Lexer<'a> {
|
||||
/// Create a new lexer for the given source contents and filename.
|
||||
pub fn from_str(s: &'a str, filename: &'a str) -> Lexer<'a> {
|
||||
let mut l = Lexer {
|
||||
filenames: vec![filename.to_string()],
|
||||
filenames: vec![filename.into()],
|
||||
file_texts: vec![s.into()],
|
||||
file_starts: vec![0],
|
||||
buf: Cow::Borrowed(s.as_bytes()),
|
||||
pos: Pos {
|
||||
@@ -82,22 +90,27 @@ impl<'a> Lexer<'a> {
|
||||
}
|
||||
|
||||
/// Create a new lexer from the given files.
|
||||
pub fn from_files(filenames: Vec<String>) -> Result<Lexer<'a>, Error> {
|
||||
pub fn from_files<S>(filenames: impl IntoIterator<Item = S>) -> Result<Lexer<'a>>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let filenames: Vec<Arc<str>> = filenames.into_iter().map(|f| f.as_ref().into()).collect();
|
||||
assert!(!filenames.is_empty());
|
||||
let file_contents: Vec<String> = filenames
|
||||
|
||||
let file_contents: Vec<Arc<str>> = filenames
|
||||
.iter()
|
||||
.map(|f| {
|
||||
use std::io::Read;
|
||||
let mut f = std::fs::File::open(f)?;
|
||||
let mut f = std::fs::File::open(&**f)?;
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s)?;
|
||||
Ok(s)
|
||||
Ok(s.into())
|
||||
})
|
||||
.collect::<Result<Vec<String>, Error>>()?;
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let mut file_starts = vec![];
|
||||
let mut buf = String::new();
|
||||
for file in file_contents {
|
||||
for file in &file_contents {
|
||||
file_starts.push(buf.len());
|
||||
buf += &file;
|
||||
buf += "\n";
|
||||
@@ -105,6 +118,7 @@ impl<'a> Lexer<'a> {
|
||||
|
||||
let mut l = Lexer {
|
||||
filenames,
|
||||
file_texts: file_contents,
|
||||
buf: Cow::Owned(buf.into_bytes()),
|
||||
file_starts,
|
||||
pos: Pos {
|
||||
|
||||
@@ -22,10 +22,13 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
|
||||
fn error(&self, pos: Pos, msg: String) -> Error {
|
||||
Error::CompileError {
|
||||
filename: self.lexer.filenames[pos.file].clone(),
|
||||
pos,
|
||||
Error::ParseError {
|
||||
msg,
|
||||
src: Source::new(
|
||||
self.lexer.filenames[pos.file].clone(),
|
||||
self.lexer.file_texts[pos.file].clone(),
|
||||
),
|
||||
span: miette::SourceSpan::from((pos.offset, 1)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +123,7 @@ impl<'a> Parser<'a> {
|
||||
Ok(Defs {
|
||||
defs,
|
||||
filenames: self.lexer.filenames.clone(),
|
||||
file_texts: self.lexer.file_texts.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,7 @@ use crate::ast;
|
||||
use crate::error::*;
|
||||
use crate::lexer::Pos;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Either `Ok(T)` or a one or more `Error`s.
|
||||
///
|
||||
/// This allows us to return multiple type errors at the same time, for example.
|
||||
pub type SemaResult<T> = std::result::Result<T, Vec<Error>>;
|
||||
use std::sync::Arc;
|
||||
|
||||
declare_id!(
|
||||
/// The id of an interned symbol.
|
||||
@@ -60,7 +56,12 @@ pub struct TypeEnv {
|
||||
/// Arena of input ISLE source filenames.
|
||||
///
|
||||
/// We refer to these indirectly through the `Pos::file` indices.
|
||||
pub filenames: Vec<String>,
|
||||
pub filenames: Vec<Arc<str>>,
|
||||
|
||||
/// Arena of input ISLE source contents.
|
||||
///
|
||||
/// We refer to these indirectly through the `Pos::file` indices.
|
||||
pub file_texts: Vec<Arc<str>>,
|
||||
|
||||
/// Arena of interned symbol names.
|
||||
///
|
||||
@@ -444,9 +445,10 @@ impl Expr {
|
||||
|
||||
impl TypeEnv {
|
||||
/// Construct the type environment from the AST.
|
||||
pub fn from_ast(defs: &ast::Defs) -> SemaResult<TypeEnv> {
|
||||
pub fn from_ast(defs: &ast::Defs) -> Result<TypeEnv> {
|
||||
let mut tyenv = TypeEnv {
|
||||
filenames: defs.filenames.clone(),
|
||||
file_texts: defs.file_texts.clone(),
|
||||
syms: vec![],
|
||||
sym_map: HashMap::new(),
|
||||
types: vec![],
|
||||
@@ -523,11 +525,11 @@ impl TypeEnv {
|
||||
Ok(tyenv)
|
||||
}
|
||||
|
||||
fn return_errors(&mut self) -> SemaResult<()> {
|
||||
if self.errors.len() > 0 {
|
||||
Err(std::mem::take(&mut self.errors))
|
||||
} else {
|
||||
Ok(())
|
||||
fn return_errors(&mut self) -> Result<()> {
|
||||
match self.errors.len() {
|
||||
0 => Ok(()),
|
||||
1 => Err(self.errors.pop().unwrap()),
|
||||
_ => Err(Error::Errors(std::mem::take(&mut self.errors))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,10 +606,13 @@ impl TypeEnv {
|
||||
}
|
||||
|
||||
fn error(&self, pos: Pos, msg: String) -> Error {
|
||||
Error::CompileError {
|
||||
filename: self.filenames[pos.file].clone(),
|
||||
pos,
|
||||
Error::TypeError {
|
||||
msg,
|
||||
src: Source::new(
|
||||
self.filenames[pos.file].clone(),
|
||||
self.file_texts[pos.file].clone(),
|
||||
),
|
||||
span: miette::SourceSpan::from((pos.offset, 1)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,7 +652,7 @@ struct BoundVar {
|
||||
|
||||
impl TermEnv {
|
||||
/// Construct the term environment from the AST and the type environment.
|
||||
pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult<TermEnv> {
|
||||
pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> Result<TermEnv> {
|
||||
let mut env = TermEnv {
|
||||
terms: vec![],
|
||||
term_map: HashMap::new(),
|
||||
@@ -689,7 +694,7 @@ impl TermEnv {
|
||||
()
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<TypeId>, ()>>();
|
||||
.collect::<std::result::Result<Vec<_>, _>>();
|
||||
let arg_tys = match arg_tys {
|
||||
Ok(a) => a,
|
||||
Err(_) => {
|
||||
|
||||
Reference in New Issue
Block a user