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:
Nick Fitzgerald
2021-09-28 14:51:02 -07:00
committed by Chris Fallin
parent 38da2cee3e
commit 6ffb02d9f6
10 changed files with 415 additions and 68 deletions

View File

@@ -7,4 +7,5 @@ license = "Apache-2.0 WITH LLVM-exception"
[dependencies]
log = "0.4"
miette = "3.0.0"
thiserror = "1.0.29"

View File

@@ -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.

View 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))

View File

@@ -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(),
)))
}
}

View File

@@ -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 {

View File

@@ -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(),
})
}

View File

@@ -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(_) => {