use crate::syntax::{LexerError, Location, Token}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::files; use lalrpop_util::ParseError; use std::io; use thiserror::Error; #[derive(Debug, Error)] pub enum Error { #[error("IO failure: {0}")] IOError(#[from] io::Error), #[error("Internal file database error: {0}")] InternalFileDBError(#[from] files::Error), #[error("Error in parser: {0}")] ParserError(#[from] ParseError), #[error("Internal error: Couldn't deal with bound variable with no bindiing site ({0})")] BindingSiteFailure(Location, String), #[error("Unbound variable '{0}'")] UnboundVariable(Location, String), #[error("Internal error: {0}")] InternalError(Location, String), } fn locations_to_labels(start: &Location, end: &Location) -> Vec> { match start { Location::Manufactured => match end { Location::Manufactured => vec![], Location::InFile(file_id, off) => vec![Label::primary(*file_id, *off..*off)], }, Location::InFile(file_id1, start) => match end { Location::InFile(file_id2, end) if file_id1 == file_id2 => { vec![Label::primary(*file_id1, *start..*end)] } _ => vec![Label::primary(*file_id1, *start..*start)], }, } } fn display_expected(expected: &[String]) -> String { match expected.len() { 0 => "".to_string(), 1 => format!("; expected {}", expected[0]), 2 => format!("; expected {} or {}", expected[0], expected[1]), n => format!( "; expected {}or {}", comma_separate(&expected[0..n - 1]), expected[n - 1] ), } } fn comma_separate(strings: &[String]) -> String { let mut result = String::new(); for s in strings.iter() { result.push_str(s); result.push_str(", "); } result } impl From for Diagnostic { fn from(x: Error) -> Self { match &x { Error::IOError(e) => Diagnostic::error().with_message(format!("{}", e)), Error::InternalFileDBError(e) => Diagnostic::error().with_message(format!("{}", e)), Error::ParserError(pe) => match pe { // this was just a token we didn't understand ParseError::InvalidToken { location } => match location { Location::Manufactured => Diagnostic::error().with_message( "encountered extremely confusing token (in generated data?!)", ), Location::InFile(file_id, off) => Diagnostic::error() .with_message("encountered extremely confusing token") .with_labels(vec![Label::primary(*file_id, *off..*off) .with_message("extremely odd token")]), }, // unexpected EOF! ParseError::UnrecognizedEOF { location, expected } => match location { Location::Manufactured => Diagnostic::error().with_message(format!( "unexpected end of file{}", display_expected(expected) )), Location::InFile(file_id, off) => Diagnostic::error() .with_message(format!( "unexpected enf of file{}", display_expected(expected) )) .with_labels(vec![Label::primary(*file_id, *off..*off)]), }, // encountered a token where it shouldn't be ParseError::UnrecognizedToken { token, expected } => { let (start, token, end) = token; let expected_str = format!("unexpected token {}{}", token, display_expected(expected)); let unexpected_str = format!("unexpected token {}", token); let mut labels = locations_to_labels(start, end); Diagnostic::error() .with_labels( labels .drain(..) .map(|l| l.with_message(unexpected_str.clone())) .collect(), ) .with_message(expected_str) } // I think we get this when we get a token, but were expected EOF ParseError::ExtraToken { token } => { let (start, token, end) = token; let expected_str = format!("unexpected token {} after the expected end of file", token); let unexpected_str = format!("unexpected token {}", token); let mut labels = locations_to_labels(start, end); Diagnostic::error() .with_labels( labels .drain(..) .map(|l| l.with_message(unexpected_str.clone())) .collect(), ) .with_message(expected_str) } // simple lexer errors ParseError::User { error } => match error { LexerError::LexFailure(location) => match location { Location::Manufactured => Diagnostic::error() .with_message("unexpected character encountered in manufactured code?"), Location::InFile(file_id, offset) => Diagnostic::error() .with_labels(vec![Label::primary(*file_id, *offset..*offset) .with_message("unexpected character")]), }, }, }, Error::BindingSiteFailure(location, name) => match location { Location::Manufactured => Diagnostic::error().with_message(format!( "Internal Error: Lost binding site for bound variable {}", name )), Location::InFile(file_id, offset) => Diagnostic::error() .with_labels(vec![ Label::primary(*file_id, *offset..*offset).with_message("discovered here") ]) .with_message(format!( "Internal Error: Lost binding site for bound variable {}", name )), }, Error::UnboundVariable(location, name) => match location { Location::Manufactured => { Diagnostic::error().with_message(format!("Unbound variable '{}'", name)) } Location::InFile(file_id, offset) => Diagnostic::error() .with_labels(vec![ Label::primary(*file_id, *offset..*offset).with_message("unbound here") ]) .with_message(format!("Unbound variable '{}'", name)), }, Error::InternalError(location, string) => match location { Location::Manufactured => { Diagnostic::error().with_message(format!("Internal error: {}", string)) } Location::InFile(file_id, offset) => Diagnostic::error() .with_labels(vec![ Label::primary(*file_id, *offset..*offset).with_message("this is related") ]) .with_message(format!("Internal error: {}", string)), }, } } }