use proc_macro2::{Literal, Span, TokenStream};
use quote::quote_spanned;
use crate::capture::{SourceMap, SourceRange};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Level {
Error,
Warning,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Note {
pub message: String,
pub range: Option<SourceRange>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Diagnostic {
pub level: Level,
pub message: String,
pub range: SourceRange,
pub notes: Vec<Note>,
pub lexical: bool,
}
impl Diagnostic {
pub fn error(range: SourceRange, message: impl Into<String>) -> Self {
Self {
level: Level::Error,
message: message.into(),
range,
notes: Vec::new(),
lexical: false,
}
}
pub fn warning(range: SourceRange, message: impl Into<String>) -> Self {
Self {
level: Level::Warning,
message: message.into(),
range,
notes: Vec::new(),
lexical: false,
}
}
pub fn at_lexing(mut self) -> Self {
self.lexical = true;
self
}
pub fn with_note(mut self, message: impl Into<String>) -> Self {
self.notes.push(Note {
message: message.into(),
range: None,
});
self
}
pub fn with_note_at(mut self, range: SourceRange, message: impl Into<String>) -> Self {
self.notes.push(Note {
message: message.into(),
range: Some(range),
});
self
}
}
#[derive(Default)]
pub struct Diagnostics {
items: Vec<Diagnostic>,
fatal: Option<String>,
}
impl Diagnostics {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, diag: Diagnostic) {
self.items.push(diag);
}
pub fn error(&mut self, range: SourceRange, message: impl Into<String>) {
self.push(Diagnostic::error(range, message));
}
pub fn warning(&mut self, range: SourceRange, message: impl Into<String>) {
self.push(Diagnostic::warning(range, message));
}
pub fn extend(&mut self, other: Diagnostics) {
self.items.extend(other.items);
if self.fatal.is_none() {
self.fatal = other.fatal;
}
}
pub fn fatal(&mut self, message: impl Into<String>) {
if self.fatal.is_none() {
self.fatal = Some(message.into());
}
}
pub fn items(&self) -> &[Diagnostic] {
&self.items
}
pub fn items_mut(&mut self) -> &mut [Diagnostic] {
&mut self.items
}
pub fn has_errors(&self) -> bool {
self.fatal.is_some() || self.items.iter().any(|d| d.level == Level::Error)
}
pub fn error_count(&self) -> usize {
self.items
.iter()
.filter(|d| d.level == Level::Error)
.count()
}
pub fn sorted(&self) -> Vec<&Diagnostic> {
let mut out: Vec<&Diagnostic> = self.items.iter().collect();
out.sort_by_key(|d| d.range.start);
out
}
pub fn to_token_stream(&self, map: &SourceMap) -> TokenStream {
let mut out = TokenStream::new();
if let Some(msg) = &self.fatal {
out.extend(compile_error_at(Span::call_site(), msg));
}
for diag in self.sorted() {
if diag.level != Level::Error {
continue;
}
let span = map.span(diag.range);
out.extend(compile_error_at(span, &self.render(map, diag)));
}
out
}
pub fn render(&self, map: &SourceMap, diag: &Diagnostic) -> String {
let mut msg = position_prefix(map, diag.range);
msg.push_str(&diag.message);
msg.push_str(&position_suffix(map, diag.range));
for note in &diag.notes {
msg.push_str("\nnote: ");
msg.push_str(¬e.message);
if let Some(range) = note.range {
msg.push_str(¬e_location(map, range));
}
}
msg
}
}
fn position_prefix(map: &SourceMap, range: SourceRange) -> String {
match map.header_position(range.start) {
Some((name, line, column)) => format!("{name}:{line}:{column}: "),
None => String::new(),
}
}
fn position_suffix(map: &SourceMap, range: SourceRange) -> String {
if map.is_precise_at(range) || map.header_position(range.start).is_some() {
return String::new();
}
let (line, column) = map.line_col(range.start);
format!(" (at line {line}, column {column} of the C source)")
}
fn note_location(map: &SourceMap, range: SourceRange) -> String {
match map.header_position(range.start) {
Some((name, line, _)) => format!(" in {name}:{line}"),
None => format!(" at line {}", map.source_line(range.start)),
}
}
pub fn compile_error_at(span: Span, message: &str) -> TokenStream {
let mut lit = Literal::string(message);
lit.set_span(span);
quote_spanned! { span => ::core::compile_error! { #lit } }
}