use std::{
fmt::{self, Display},
fs,
panic::Location,
};
use crate::MietteError;
pub trait Diagnostic: std::error::Error {
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
None
}
fn severity(&self) -> Option<Severity> {
None
}
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
None
}
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
None
}
fn source_code(&self) -> Option<&dyn SourceCode> {
None
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
None
}
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
None
}
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
None
}
}
impl std::error::Error for Box<dyn Diagnostic> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
(**self).source()
}
fn cause(&self) -> Option<&dyn std::error::Error> {
self.source()
}
}
impl<T: Diagnostic + Send + Sync + 'static> From<T>
for Box<dyn Diagnostic + Send + Sync + 'static>
{
fn from(diag: T) -> Self {
Box::new(diag)
}
}
impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + Send + 'static> {
fn from(diag: T) -> Self {
Box::<dyn Diagnostic + Send + Sync>::from(diag)
}
}
impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + 'static> {
fn from(diag: T) -> Self {
Box::<dyn Diagnostic + Send + Sync>::from(diag)
}
}
impl From<&str> for Box<dyn Diagnostic> {
fn from(s: &str) -> Self {
From::from(String::from(s))
}
}
impl<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
fn from(s: &str) -> Self {
From::from(String::from(s))
}
}
impl From<String> for Box<dyn Diagnostic> {
fn from(s: String) -> Self {
let err1: Box<dyn Diagnostic + Send + Sync> = From::from(s);
let err2: Box<dyn Diagnostic> = err1;
err2
}
}
impl From<String> for Box<dyn Diagnostic + Send + Sync> {
fn from(s: String) -> Self {
struct StringError(String);
impl std::error::Error for StringError {}
impl Diagnostic for StringError {}
impl Display for StringError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl fmt::Debug for StringError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
Box::new(StringError(s))
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
#[derive(thiserror::Error)]
#[error(transparent)]
struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
impl fmt::Debug for BoxedDiagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl Diagnostic for BoxedDiagnostic {}
Box::new(BoxedDiagnostic(s))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Severity {
Error,
Warning,
Advice,
}
pub trait SourceCode: Send + Sync {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LabeledSpan {
label: Option<String>,
span: SourceSpan,
}
impl LabeledSpan {
pub fn new(label: Option<String>, offset: ByteOffset, len: ByteOffset) -> Self {
Self {
label,
span: (offset, len).into(),
}
}
pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
Self {
label,
span: span.into(),
}
}
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
pub fn inner(&self) -> &SourceSpan {
&self.span
}
pub fn offset(&self) -> usize {
self.span.offset()
}
pub fn len(&self) -> usize {
self.span.len()
}
pub fn is_empty(&self) -> bool {
self.span.is_empty()
}
}
pub trait SpanContents<'a> {
fn data(&self) -> &'a [u8];
fn span(&self) -> &SourceSpan;
fn name(&self) -> Option<&str> {
None
}
fn line(&self) -> usize;
fn column(&self) -> usize;
fn line_count(&self) -> usize;
}
#[derive(Clone, Debug)]
pub struct MietteSpanContents<'a> {
data: &'a [u8],
span: SourceSpan,
line: usize,
column: usize,
line_count: usize,
name: Option<String>,
}
impl<'a> MietteSpanContents<'a> {
pub fn new(
data: &'a [u8],
span: SourceSpan,
line: usize,
column: usize,
line_count: usize,
) -> MietteSpanContents<'a> {
MietteSpanContents {
data,
span,
line,
column,
line_count,
name: None,
}
}
pub fn new_named(
name: String,
data: &'a [u8],
span: SourceSpan,
line: usize,
column: usize,
line_count: usize,
) -> MietteSpanContents<'a> {
MietteSpanContents {
data,
span,
line,
column,
line_count,
name: Some(name),
}
}
}
impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
fn data(&self) -> &'a [u8] {
self.data
}
fn span(&self) -> &SourceSpan {
&self.span
}
fn line(&self) -> usize {
self.line
}
fn column(&self) -> usize {
self.column
}
fn line_count(&self) -> usize {
self.line_count
}
fn name(&self) -> Option<&str> {
self.name.as_deref()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct SourceSpan {
offset: SourceOffset,
length: SourceOffset,
}
impl SourceSpan {
pub fn new(start: SourceOffset, length: SourceOffset) -> Self {
Self {
offset: start,
length,
}
}
pub fn offset(&self) -> usize {
self.offset.offset()
}
pub fn len(&self) -> usize {
self.length.offset()
}
pub fn is_empty(&self) -> bool {
self.length.offset() == 0
}
}
impl From<(ByteOffset, ByteOffset)> for SourceSpan {
fn from((start, len): (ByteOffset, ByteOffset)) -> Self {
Self {
offset: start.into(),
length: len.into(),
}
}
}
impl From<(SourceOffset, SourceOffset)> for SourceSpan {
fn from((start, len): (SourceOffset, SourceOffset)) -> Self {
Self {
offset: start,
length: len,
}
}
}
impl From<std::ops::Range<ByteOffset>> for SourceSpan {
fn from(range: std::ops::Range<ByteOffset>) -> Self {
Self {
offset: range.start.into(),
length: range.len().into(),
}
}
}
impl From<SourceOffset> for SourceSpan {
fn from(offset: SourceOffset) -> Self {
Self {
offset,
length: 0.into(),
}
}
}
impl From<ByteOffset> for SourceSpan {
fn from(offset: ByteOffset) -> Self {
Self {
offset: offset.into(),
length: 0.into(),
}
}
}
pub type ByteOffset = usize;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct SourceOffset(ByteOffset);
impl SourceOffset {
pub fn offset(&self) -> ByteOffset {
self.0
}
pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
let mut line = 0usize;
let mut col = 0usize;
let mut offset = 0usize;
for char in source.as_ref().chars() {
if line + 1 >= loc_line && col + 1 >= loc_col {
break;
}
if char == '\n' {
col = 0;
line += 1;
} else {
col += 1;
}
offset += char.len_utf8();
}
SourceOffset(offset)
}
#[track_caller]
pub fn from_current_location() -> Result<(String, Self), MietteError> {
let loc = Location::caller();
Ok((
loc.file().into(),
fs::read_to_string(loc.file())
.map(|txt| Self::from_location(&txt, loc.line() as usize, loc.column() as usize))?,
))
}
}
impl From<ByteOffset> for SourceOffset {
fn from(bytes: ByteOffset) -> Self {
SourceOffset(bytes)
}
}
#[test]
fn test_source_offset_from_location() {
let source = "f\n\noo\r\nbar";
assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
assert_eq!(
SourceOffset::from_location(source, 5, 1).offset(),
source.len()
);
}