use std::{
error, fmt,
ops::{Deref, DerefMut, Range},
path::{Path, PathBuf},
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SpanPosition {
pub row: usize,
pub col: usize,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Spanned<T> {
node: T,
file_path: PathBuf,
span: Range<usize>,
}
impl<T> Spanned<T> {
pub fn new(node: T) -> Self {
Self {
node,
file_path: PathBuf::default(),
span: Range::default(),
}
}
pub fn map_spanned<T2, F: Fn(T) -> T2>(self, f: F) -> Spanned<T2> {
let node = f(self.node);
Spanned {
node,
file_path: self.file_path,
span: self.span,
}
}
#[must_use]
pub fn with_span(mut self, span: Range<usize>) -> Self {
self.span = span;
self
}
pub fn set_span(&mut self, span: Range<usize>) {
self.span = span;
}
pub fn span(&self) -> &Range<usize> {
&self.span
}
#[must_use]
pub fn with_file_path<P: AsRef<Path>>(mut self, file_path: P) -> Self {
self.file_path = file_path.as_ref().into();
self
}
pub fn set_file_path<P: AsRef<Path>>(&mut self, file_path: P) {
self.file_path = file_path.as_ref().into();
}
pub fn file_path(&self) -> &Path {
&self.file_path
}
pub fn is_dummy_span(&self) -> bool {
self.span.end == 0 && self.span.start == 0
}
pub fn get_position(&self, contents: &str) -> Option<SpanPosition> {
let section_before = &contents.get(..self.span.start)?;
let row = section_before.matches('\n').count() + 1;
let last_line = section_before
.rfind('\n')
.map_or(0, |last_line| last_line + 1);
let col = section_before[last_line..].chars().count() + 1;
Some(SpanPosition { row, col })
}
pub fn into_inner(self) -> T {
self.node
}
}
impl<T> From<T> for Spanned<T> {
fn from(node: T) -> Self {
Self::new(node)
}
}
impl<T> Deref for Spanned<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.node
}
}
impl<T> DerefMut for Spanned<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.node
}
}
impl<T: fmt::Display> fmt::Display for Spanned<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.node.fmt(f)
}
}
impl<T: error::Error> error::Error for Spanned<T> {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.node.source()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn utf8() {
let source = "It is \u{2600}\u{fe0f} today!";
let span = Spanned::new(()).with_span(12..18);
assert_eq!(
span.get_position(source),
Some(SpanPosition { row: 1, col: 9 })
);
}
}