diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index b55178b6d5..f4bf43116d 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -2,6 +2,30 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -22,12 +46,39 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "cc" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" + [[package]] name = "cfg-if" version = "1.0.0" @@ -44,7 +95,7 @@ dependencies = [ "atty", "bitflags", "strsim", - "textwrap", + "textwrap 0.11.0", "unicode-width", "vec_map", ] @@ -58,6 +109,12 @@ dependencies = [ "log", ] +[[package]] +name = "gimli" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -67,11 +124,18 @@ dependencies = [ "libc", ] +[[package]] +name = "is_ci" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" + [[package]] name = "isle" version = "0.1.0" dependencies = [ "log", + "miette", "thiserror", ] @@ -83,6 +147,7 @@ dependencies = [ "env_logger", "isle", "log", + "miette", ] [[package]] @@ -100,6 +165,73 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "miette" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "024831248cacc3305f5bb33d9daf9df54d6d95bf462c58f388845a17388c47fe" +dependencies = [ + "atty", + "backtrace", + "miette-derive", + "once_cell", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "term_size", + "textwrap 0.14.2", + "thiserror", +] + +[[package]] +name = "miette-derive" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074acd9c89172a516def5d82b8d90fb724fecba9dcae6fcdd88a75689601349f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "object" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "owo-colors" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a61765925aec40abdb23812a3a1a01fafc6ffb9da22768b2ce665a9e84e527c" + [[package]] name = "proc-macro2" version = "1.0.29" @@ -118,12 +250,69 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "supports-color" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f5b0f9e689dd52e27228469dd68b7416b60d75b7571ae9060a5f4c50048fee" +dependencies = [ + "atty", + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406" +dependencies = [ + "atty", +] + +[[package]] +name = "supports-unicode" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2" +dependencies = [ + "atty", +] + [[package]] name = "syn" version = "1.0.77" @@ -135,6 +324,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -144,6 +343,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.29" @@ -164,6 +374,15 @@ dependencies = [ "syn", ] +[[package]] +name = "unicode-linebreak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" +dependencies = [ + "regex", +] + [[package]] name = "unicode-width" version = "0.1.8" diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index 8600e84129..dec3145407 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -7,4 +7,5 @@ license = "Apache-2.0 WITH LLVM-exception" [dependencies] log = "0.4" +miette = "3.0.0" thiserror = "1.0.29" diff --git a/cranelift/isle/isle/src/ast.rs b/cranelift/isle/isle/src/ast.rs index 489e0b81d1..f256e9cc1d 100644 --- a/cranelift/isle/isle/src/ast.rs +++ b/cranelift/isle/isle/src/ast.rs @@ -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, - pub filenames: Vec, + pub filenames: Vec>, + pub file_texts: Vec>, } /// One toplevel form in an ISLE file. diff --git a/cranelift/isle/isle/src/compile.rs b/cranelift/isle/isle/src/compile.rs index 68304852e7..c33b740674 100644 --- a/cranelift/isle/isle/src/compile.rs +++ b/cranelift/isle/isle/src/compile.rs @@ -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> { +pub fn compile(defs: &ast::Defs) -> Result { let mut typeenv = sema::TypeEnv::from_ast(defs)?; let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?; Ok(codegen::codegen(&typeenv, &termenv)) diff --git a/cranelift/isle/isle/src/error.rs b/cranelift/isle/isle/src/error.rs index 6b2d30ecaf..f9278a3365 100644 --- a/cranelift/isle/isle/src/error.rs +++ b/cranelift/isle/isle/src/error.rs @@ -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 = std::result::Result; /// 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), - /// 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), } impl From for Error { @@ -28,3 +58,81 @@ impl From for Error { Error::IoError(Arc::new(e)) } } + +impl From> for Error { + fn from(es: Vec) -> 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, + text: Arc, +} + +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", &""); + Ok(()) + } +} + +impl Source { + pub(crate) fn new(name: Arc, text: Arc) -> Self { + Self { name, text } + } + + /// Get this source's file name. + pub fn name(&self) -> &Arc { + &self.name + } + + /// Get this source's text contents. + pub fn text(&self) -> &Arc { + &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 + '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(), + ))) + } +} diff --git a/cranelift/isle/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs index 9a8fa92bb8..f71bd053ff 100644 --- a/cranelift/isle/isle/src/lexer.rs +++ b/cranelift/isle/isle/src/lexer.rs @@ -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, + pub filenames: Vec>, + + /// Arena of file source texts. + /// + /// Indexed via `Pos::file`. + pub file_texts: Vec>, + file_starts: Vec, 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]) -> 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]) -> 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) -> Result, Error> { + pub fn from_files(filenames: impl IntoIterator) -> Result> + where + S: AsRef, + { + let filenames: Vec> = filenames.into_iter().map(|f| f.as_ref().into()).collect(); assert!(!filenames.is_empty()); - let file_contents: Vec = filenames + + let file_contents: Vec> = 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::, Error>>()?; + .collect::>()?; 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 { diff --git a/cranelift/isle/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs index ae0a3b0059..4588e1c99b 100644 --- a/cranelift/isle/isle/src/parser.rs +++ b/cranelift/isle/isle/src/parser.rs @@ -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(), }) } diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 148a8c0c07..2082079e5e 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -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 = std::result::Result>; +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, + pub filenames: Vec>, + + /// Arena of input ISLE source contents. + /// + /// We refer to these indirectly through the `Pos::file` indices. + pub file_texts: Vec>, /// 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 { + pub fn from_ast(defs: &ast::Defs) -> Result { 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 { + pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> Result { let mut env = TermEnv { terms: vec![], term_map: HashMap::new(), @@ -689,7 +694,7 @@ impl TermEnv { () }) }) - .collect::, ()>>(); + .collect::, _>>(); let arg_tys = match arg_tys { Ok(a) => a, Err(_) => { diff --git a/cranelift/isle/islec/Cargo.toml b/cranelift/isle/islec/Cargo.toml index 4454234640..61230c0e98 100644 --- a/cranelift/isle/islec/Cargo.toml +++ b/cranelift/isle/islec/Cargo.toml @@ -10,3 +10,4 @@ log = "0.4" isle = { version = "*", path = "../isle/" } env_logger = { version = "0.8", default-features = false } clap = "2.33" +miette = { version = "3.0.0", features = ["fancy"] } diff --git a/cranelift/isle/islec/src/main.rs b/cranelift/isle/islec/src/main.rs index be99d7301f..dd41a0c391 100644 --- a/cranelift/isle/islec/src/main.rs +++ b/cranelift/isle/islec/src/main.rs @@ -1,10 +1,20 @@ use clap::{App, Arg}; +use isle::{compile, lexer, parser}; +use miette::{IntoDiagnostic, Result}; -use isle::{error, lexer, parser, compile}; - -fn main() -> Result<(), error::Error> { +fn main() -> Result<()> { let _ = env_logger::try_init(); + let _ = miette::set_hook(Box::new(|_| { + Box::new( + miette::MietteHandlerOpts::new() + // `miette` mistakenly uses braille-optimized output for emacs's + // `M-x shell`. + .force_graphical(true) + .build(), + ) + })); + let matches = App::new("isle") .version(env!("CARGO_PKG_VERSION")) .author("Chris Fallin ") @@ -37,31 +47,14 @@ fn main() -> Result<(), error::Error> { let lexer = lexer::Lexer::from_files(input_files)?; let mut parser = parser::Parser::new(lexer); - let defs = match parser.parse_defs() { - Ok(defs) => defs, - Err(error) => { - eprintln!("{}", error); - eprintln!("Failed to parse input."); - std::process::exit(1); - } - }; - let code = match compile::compile(&defs) { - Ok(code) => code, - Err(errors) => { - for error in errors { - eprintln!("{}", error); - } - eprintln!("Failed to compile."); - std::process::exit(1); - } - }; + let defs = parser.parse_defs()?; + let code = compile::compile(&defs)?; { use std::io::Write; - let mut f = std::fs::File::create(output_file)?; - writeln!(&mut f, "{}", code)?; + let mut f = std::fs::File::create(output_file).into_diagnostic()?; + writeln!(&mut f, "{}", code).into_diagnostic()?; } Ok(()) } -