spanner 0.2.0

map source code positions to easy-to-use structs
Documentation
use {
	crate::{line_col, Loc, Span},
	::core::{
		fmt::Debug,
		hash::{Hash, Hasher},
		ops::{Index, Range},
	},
};

/// source code for a buffer
pub trait BufferSource: Debug + Default {
	/// the content in the buffer
	///
	/// this should be cheap and always return the same value
	fn source(&self) -> &str;

	/// the name of this buffer
	fn name(&self) -> Option<&str> {
		None
	}
}

impl BufferSource for String {
	fn source(&self) -> &str {
		self
	}
}

/// a buffer containing some source code
#[derive(Debug)]
pub struct Buffer<Src: BufferSource> {
	/// buffer index
	pub(crate) index: u16,
	/// the unique linear index span of this buffer
	pub linear_span: Range<usize>,
	/// [`BufferSource`] for this buffer
	pub src: Src,
	/// the byte offset into this buffer of the start of each line
	pub line_beginnings: Vec<u32>,
}

impl<Src: BufferSource> Buffer<Src> {
	/// where this buffer begins
	pub fn start(&self) -> Loc {
		Loc {
			buf: self.index,
			pos: 0,
		}
	}

	/// where this buffer ends
	#[allow(clippy::cast_possible_truncation, reason = "documented")]
	pub fn end(&self) -> Loc {
		Loc {
			buf: self.index,
			pos: self.len() as u32,
		}
	}

	/// the span of this buffer
	#[allow(clippy::cast_possible_truncation, reason = "documented")]
	pub fn span(&self) -> Span {
		Span {
			start: 0,
			end: self.src.source().len() as u32,
			buf: self.index,
		}
	}

	/// whether the buffer is empty
	pub fn is_empty(&self) -> bool {
		self.len() == 0
	}

	/// the length of the buffer's source code
	pub fn len(&self) -> usize {
		self.src.source().len()
	}

	/// the number of lines in the buffer
	pub fn len_lines(&self) -> usize {
		self.line_beginnings.len()
	}

	/// whether the buffer contains a given location
	#[allow(clippy::cast_possible_truncation, reason = "documented")]
	pub fn contains(&self, loc: Loc) -> bool {
		loc.buf == self.index && loc.pos < self.len() as u32
	}

	/// whether the buffer contains a given span
	#[allow(clippy::cast_possible_truncation, reason = "documented")]
	pub fn contains_span(&self, span: Span) -> bool {
		span.buf == self.index && span.end <= self.len() as u32
	}

	/// slice the source code
	///
	/// this is equivalent to `&buffer[span]`
	pub fn src_slice(&self, span: Span) -> &str {
		&self[span]
	}

	/// find the [`Loc`] of a given [`LineCol`](line_col::LineCol)
	pub fn loc_of(&self, line_col: &line_col::LineCol) -> Loc {
		Loc {
			pos: self.line_beginnings[line_col.line as usize] + line_col.col,
			buf: self.index,
		}
	}

	/// find the [`LineCol`](line_col::LineCol) of a given [`Loc`]
	///
	/// # Panics
	///
	/// if the location is in a different buffer
	pub fn line_col(&self, loc: Loc) -> line_col::LineCol {
		assert_eq!(
			loc.buf, self.index,
			"looking up line_col for a position in a different buffer"
		);
		line_col::find_line_col(&self.line_beginnings[..], loc.pos)
	}
}

impl<Src: BufferSource> Index<Span> for Buffer<Src> {
	type Output = str;

	fn index(&self, span: Span) -> &Self::Output {
		assert_eq!(
			span.buf, self.index,
			"looking up line_col for a position in a different buffer"
		);
		&self.src.source()[span.start as usize..span.end as usize]
	}
}

impl<Src: BufferSource> Hash for Buffer<Src> {
	fn hash<H: Hasher>(&self, state: &mut H) {
		self.index.hash(state);
	}
}

impl<Src: BufferSource> PartialEq for Buffer<Src> {
	fn eq(&self, rhs: &Self) -> bool {
		self.index == rhs.index
	}
}

impl<Src: BufferSource> Eq for Buffer<Src> {}