spanner 0.2.0

map source code positions to easy-to-use structs
Documentation
use {
	::memchr::memchr_iter,
	::tyfling::{debug, display},
};

/// a zero-indexed line and column
#[derive(Hash, PartialEq, Eq)]
#[debug("[ {self} ]")]
#[display("{line}:{col}")]
pub struct LineCol {
	/// the line
	pub line: u32,
	/// the column
	pub col: u32,
}

/// calculate byte offsets for the start of each line
#[allow(clippy::cast_possible_truncation, reason = "documented")]
#[must_use]
pub fn calc_line_beginnings(src: &str) -> Vec<u32> {
	let mut lb = Vec::with_capacity(8);
	lb.push(0);
	lb.extend(memchr_iter(b'\n', src.as_bytes()).map(|i| i as u32 + 1));
	lb
}

/// find the line number for a given offset, passing the [line beginnings](calc_line_beginnings) and byte offset
#[allow(clippy::cast_possible_truncation, reason = "documented")]
#[must_use]
pub fn find_line(lb: &[u32], pos: u32) -> u32 {
	(match lb.binary_search(&pos) {
		Ok(i) => i,
		Err(i) => i - 1,
	}) as u32
}

/// find the line and column for a given offset, passing the [line beginnings](calc_line_beginnings) and byte offset
#[allow(clippy::cast_possible_truncation, reason = "documented")]
#[must_use]
pub fn find_line_col(lb: &[u32], pos: u32) -> LineCol {
	let line = find_line(lb, pos);
	LineCol {
		line,
		col: pos - lb[line as usize],
	}
}