use crate::engine::{Engine, FileId};
use comemo::TrackedMut;
use ecow::{EcoString, EcoVec};
use rustre_parser::ast::{AstNode, AstToken, Root, Str};
use rustre_parser::{SyntaxElement, SyntaxNode, SyntaxToken};
use std::fmt::{Debug, Formatter};
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct Span<F = FileId> {
pub root: F,
pub start: usize,
pub end: usize,
}
impl Debug for Span {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.start..self.end)
}
}
fn preceding_trivia_len(syntax: &SyntaxNode) -> usize {
syntax
.children_with_tokens()
.map_while(|el| match el {
SyntaxElement::Token(t) if !t.kind().is_trivia() => None,
SyntaxElement::Token(t) => Some(t.text().len()),
SyntaxElement::Node(n) => Some(preceding_trivia_len(&n)),
})
.sum()
}
impl Span<Root> {
pub fn of_token(syntax_token: &SyntaxToken) -> Self {
let range = syntax_token.text_range();
let root = Root::expect(syntax_token.parent_ancestors().last().unwrap());
Span {
root,
start: range.start().into(),
end: range.end().into(),
}
}
pub fn of_node(syntax_node: &SyntaxNode) -> Self {
let to_skip = preceding_trivia_len(syntax_node);
let range = syntax_node.text_range();
let root = Root::expect(
syntax_node
.ancestors()
.last()
.unwrap_or(syntax_node.clone()),
);
Span {
root,
start: to_skip + usize::from(range.start()),
end: range.end().into(),
}
}
pub fn of_lustre_str(string_node: &Str) -> Self {
let mut span = Self::of_token(string_node.syntax());
span.start += 1;
span.end -= 1;
span
}
pub fn after(mut self) -> Self {
self.start = self.end;
self
}
pub fn extend(&mut self, other: impl IntoIterator<Item = Self>) {
let (min, max) = other
.into_iter()
.fold((self.start, self.end), |(acc_s, acc_e), span| {
(
std::cmp::min(acc_s, span.start),
std::cmp::max(acc_e, span.end),
)
});
self.start = min;
self.end = max;
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[must_use]
pub struct Diagnostic {
pub level: Level,
pub message: EcoString,
pub attachments: EcoVec<(Span, EcoString)>,
pub notes: Vec<EcoString>,
}
impl Diagnostic {
pub fn build(level: Level, message: impl Into<EcoString>) -> DiagnosticBuilder {
DiagnosticBuilder {
level,
message: message.into(),
attachments: Vec::with_capacity(1),
notes: Vec::new(),
}
}
pub fn file_context(&self) -> Option<(FileId, usize)> {
self.attachments
.first()
.map(|(span, _)| (span.root.clone(), span.start))
}
}
pub struct DiagnosticBuilder {
pub level: Level,
pub message: EcoString,
pub attachments: Vec<(Span<Root>, EcoString)>,
pub notes: Vec<EcoString>,
}
impl DiagnosticBuilder {
pub fn with_attachment(mut self, span: Span<Root>, message: impl Into<EcoString>) -> Self {
self.attachments.push((span, message.into()));
self
}
pub fn with_note(mut self, message: impl Into<EcoString>) -> Self {
self.notes.push(format!("note: {}", message.into()).into());
self
}
pub fn with_help(mut self, message: impl Into<EcoString>) -> Self {
self.notes.push(format!("help: {}", message.into()).into());
self
}
pub fn emit(self, engine: &mut TrackedMut<Engine>) {
let attachments = self
.attachments
.into_iter()
.map(|(span, message)| {
let file =
crate::enclosing_file_of_root(TrackedMut::reborrow_mut(engine), span.root);
let span = Span {
root: file,
start: span.start,
end: span.end,
};
(span, message)
})
.collect();
engine.emit(Diagnostic {
level: self.level,
message: self.message,
attachments,
notes: self.notes,
});
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Level {
Warning,
Error,
}