use codespan::{FileId, Span};
use std::fmt::Write;
use std::io;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use crate::codegen::{Context, ImportCtx};
use codespan_reporting::diagnostic as crd;
use codespan_reporting::diagnostic::Label;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use lalrpop_util::lexer::Token;
use lalrpop_util::ParseError;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct GlobalSpan {
pub file: FileId,
pub span: Span,
}
impl GlobalSpan {
pub fn new(file: FileId, span: Span) -> Self {
GlobalSpan { file, span }
}
}
pub enum AnyDiagnostic {
CyclicImport(CyclicImport),
ImportFailed(ImportFailed),
InvalidToken(InvalidToken),
UnexpectedEof(UnexpectedEof),
UnexpectedToken(UnexpectedToken),
UserError(UserError),
UndeclaredType(UndeclaredType),
DuplicatePolyType(DuplicatePolyType),
}
fn token_to_span((left, _t, right): &(usize, Token, usize)) -> Span {
Span::new(*left as u32, *right as u32)
}
impl AnyDiagnostic {
pub fn from_parse_error(file: FileId, e: &ParseError<usize, Token, &'static str>) -> Self {
match e {
ParseError::InvalidToken { location } => {
AnyDiagnostic::InvalidToken(InvalidToken::new(file, *location))
}
ParseError::UnrecognizedEOF { location, expected } => {
AnyDiagnostic::UnexpectedEof(UnexpectedEof::new(file, *location, expected.clone()))
}
ParseError::UnrecognizedToken { token, expected } => {
AnyDiagnostic::UnexpectedToken(UnexpectedToken::new(
GlobalSpan::new(file, token_to_span(token)),
expected.clone(),
))
}
ParseError::ExtraToken { token } => AnyDiagnostic::UnexpectedToken(
UnexpectedToken::new(GlobalSpan::new(file, token_to_span(token)), Vec::new()),
),
ParseError::User { error } => {
AnyDiagnostic::UserError(UserError::new(file, error.to_string()))
}
}
}
}
impl Diagnostic for AnyDiagnostic {
fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
match self {
AnyDiagnostic::CyclicImport(d) => d.to_codespan(ctx),
AnyDiagnostic::ImportFailed(d) => d.to_codespan(ctx),
AnyDiagnostic::InvalidToken(d) => d.to_codespan(ctx),
AnyDiagnostic::UnexpectedEof(d) => d.to_codespan(ctx),
AnyDiagnostic::UnexpectedToken(d) => d.to_codespan(ctx),
AnyDiagnostic::UserError(d) => d.to_codespan(ctx),
AnyDiagnostic::UndeclaredType(d) => d.to_codespan(ctx),
AnyDiagnostic::DuplicatePolyType(d) => d.to_codespan(ctx),
}
}
}
pub trait Diagnostic {
fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId>;
}
pub trait DiagnosticExt: Diagnostic {
fn report_and_exit(&self, ctx: &Context) -> ! {
let report = self.to_codespan(ctx);
let mut writer = StandardStream::stderr(ColorChoice::Auto);
let config = codespan_reporting::term::Config::default();
codespan_reporting::term::emit(&mut writer, &config, &ctx.files, &report).unwrap();
std::process::exit(1);
}
}
impl<T: Diagnostic> DiagnosticExt for T {}
pub struct CyclicImport {
import: Label<FileId>,
first_caused_by: Option<Label<FileId>>,
stack: Vec<FileId>,
}
impl CyclicImport {
pub(crate) fn new(call_stack: &ImportCtx) -> Self {
let imp_loc = call_stack.location.unwrap();
let mut stack = Vec::new();
let mut first_caused_by = None;
for el in call_stack.iter() {
stack.push(el.imported);
if let Some(s) = el.location {
if s.file == call_stack.imported {
first_caused_by =
Some(Label::secondary(s.file, s.span).with_message("cycle entered here"));
stack.push(s.file);
break;
}
}
}
CyclicImport {
import: Label::primary(imp_loc.file, imp_loc.span)
.with_message("file eventually importing itself"),
first_caused_by,
stack,
}
}
}
impl Diagnostic for CyclicImport {
fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
let mut labels = vec![self.import.clone()];
labels.extend(self.first_caused_by.clone());
let mut iter = self.stack.iter().rev();
let first_file = *iter.next().unwrap();
let first_name = Path::new(ctx.files.name(first_file))
.file_name()
.unwrap()
.to_str()
.unwrap();
let second_file = *iter.next().unwrap();
let mut note = if first_file == second_file {
format!("cycle occurs because {} imports itself", first_name)
} else {
let second_name = Path::new(ctx.files.name(second_file))
.file_name()
.unwrap()
.to_str()
.unwrap();
format!(
"cycle occurs because {} imports {}",
first_name, second_name
)
};
while let Some(el) = iter.next() {
let file_name = Path::new(ctx.files.name(*el))
.file_name()
.unwrap()
.to_str()
.unwrap();
write!(note, ",\n which imports {}", file_name).unwrap();
}
crd::Diagnostic::error()
.with_code("E0101")
.with_message("cyclic import")
.with_labels(labels)
.with_notes(vec![note])
}
}
pub struct ImportFailed {
path: PathBuf,
import: GlobalSpan,
errors: Vec<io::Error>,
}
impl ImportFailed {
pub fn new(path: PathBuf, import: GlobalSpan, errors: Vec<io::Error>) -> Self {
ImportFailed {
path,
import,
errors,
}
}
}
impl Diagnostic for ImportFailed {
fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
let mut notes = Vec::new();
if ctx.include_paths.is_empty() {
notes.push("no include paths listed".to_string());
}
for err in &self.errors {
if err.kind() != ErrorKind::NotFound {
notes.push(format!("I/O error: {}", err));
}
}
let import =
Label::primary(self.import.file, self.import.span).with_message("imported from here");
crd::Diagnostic::error()
.with_code("E0102")
.with_message(format!("failed to import '{}'", self.path.display()))
.with_labels(vec![import])
.with_notes(notes)
}
}
pub struct InvalidToken {
file: FileId,
location: usize,
}
impl InvalidToken {
pub fn new(file: FileId, location: usize) -> Self {
InvalidToken { file, location }
}
}
impl Diagnostic for InvalidToken {
fn to_codespan(&self, _ctx: &Context) -> crd::Diagnostic<FileId> {
let label = Label::primary(self.file, self.location..self.location + 1);
crd::Diagnostic::error()
.with_code("E0201")
.with_message("invalid token")
.with_labels(vec![label])
}
}
pub struct UnexpectedEof {
file: FileId,
location: usize,
expected: Vec<String>,
}
impl UnexpectedEof {
pub fn new(file: FileId, location: usize, expected: Vec<String>) -> Self {
UnexpectedEof {
file,
location,
expected,
}
}
}
impl Diagnostic for UnexpectedEof {
fn to_codespan(&self, _ctx: &Context) -> crd::Diagnostic<FileId> {
let label = Label::primary(self.file, self.location..self.location + 1);
let notes = make_token_list(&self.expected).into_iter().collect();
crd::Diagnostic::error()
.with_code("E0202")
.with_message("unexpected end of file")
.with_labels(vec![label])
.with_notes(notes)
}
}
pub struct UnexpectedToken {
token: GlobalSpan,
expected: Vec<String>,
}
impl UnexpectedToken {
pub fn new(token: GlobalSpan, expected: Vec<String>) -> Self {
UnexpectedToken { token, expected }
}
}
impl Diagnostic for UnexpectedToken {
fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
let text = ctx
.files
.source_slice(self.token.file, self.token.span)
.unwrap();
let label = Label::primary(self.token.file, self.token.span);
let notes = make_token_list(&self.expected).into_iter().collect();
crd::Diagnostic::error()
.with_code("E0203")
.with_message(format!("unexpected token '{}'", text))
.with_labels(vec![label])
.with_notes(notes)
}
}
pub struct UserError {
file: FileId,
text: String,
}
impl UserError {
pub fn new(file: FileId, text: String) -> Self {
UserError { file, text }
}
}
impl Diagnostic for UserError {
fn to_codespan(&self, _ctx: &Context) -> crd::Diagnostic<FileId> {
let label = Label::primary(self.file, 0..1);
crd::Diagnostic::error()
.with_code("E02XX")
.with_message(&self.text)
.with_labels(vec![label])
.with_notes(vec![
"no location info, the error may be anywhere in the file".to_string(),
])
}
}
pub struct UndeclaredType {
token: GlobalSpan,
}
impl UndeclaredType {
pub fn new(token: GlobalSpan) -> Self {
UndeclaredType { token }
}
}
impl Diagnostic for UndeclaredType {
fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
let text = ctx
.files
.source_slice(self.token.file, self.token.span)
.unwrap();
let label = Label::primary(self.token.file, self.token.span);
crd::Diagnostic::error()
.with_code("E0301")
.with_message(format!("undeclared type '{}'", text))
.with_labels(vec![label])
}
}
pub struct DuplicatePolyType {
file: FileId,
poly_params: Vec<Span>,
target: Span,
}
impl DuplicatePolyType {
pub fn new(file: FileId, poly_params: Vec<Span>, target: Span) -> Self {
DuplicatePolyType {
file,
poly_params,
target,
}
}
}
impl Diagnostic for DuplicatePolyType {
fn to_codespan(&self, ctx: &Context) -> crd::Diagnostic<FileId> {
let mut labels = Vec::new();
labels.extend(
self.poly_params
.iter()
.map(|v| Label::primary(self.file, *v)),
);
let target_name = ctx.files.source_slice(self.file, self.target).unwrap();
labels.push(Label::secondary(self.file, self.target).with_message("the target parameter"));
let notes = vec!["a parameter referred to in a [poly()] attribute on another parameter receives the IID for that parameter; as such, the relationship needs to be unique".to_string()];
crd::Diagnostic::error()
.with_code("E0302")
.with_message(format!(
"parameter '{}' used as poly-type more than once",
target_name
))
.with_labels(labels)
.with_notes(notes)
}
}
fn make_token_list(t: &[String]) -> Option<String> {
if !t.is_empty() {
let mut s = format!("expected ");
let mut iter = t.iter();
let mut cur = iter.next().unwrap();
let mut next = iter.next();
let mut i = 0;
loop {
match (i, next) {
(0, None) => {
write!(s, "{}", cur).unwrap();
}
(0, Some(_)) => {
write!(s, "one of {}", cur).unwrap();
}
(1, None) => {
write!(s, " or {}", cur).unwrap();
}
(_, None) => {
write!(s, ", or {}", cur).unwrap();
}
(_, Some(_)) => {
write!(s, ", {}", cur).unwrap();
}
}
cur = match next {
None => break,
Some(s) => s,
};
next = iter.next();
i += 1;
}
Some(s)
} else {
None
}
}