use std::fmt;
use super::{Input, Position};
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash)]
pub struct Span {
start: Position,
end: Position,
}
impl Span {
#[inline]
pub fn new(start: Position, end: Position) -> Self {
assert!(start <= end, "start > end");
Self { start, end }
}
#[inline]
pub const fn new_at(pos: Position) -> Self {
Self {
start: pos,
end: pos,
}
}
#[inline]
pub const fn start(&self) -> Position {
self.start
}
#[inline]
pub const fn end(&self) -> Position {
self.end
}
#[inline]
pub const fn offset(&self) -> usize {
self.start.offset()
}
#[inline]
pub const fn line(&self) -> usize {
self.start.line()
}
#[inline]
pub const fn column(&self) -> usize {
self.start.column()
}
pub fn line_str<'a>(&self, source: &'a str) -> &'a str {
let column0 = self.before(source).0 - 1;
let line = source[self.start.offset() - column0..].as_bytes();
unsafe {
std::str::from_utf8_unchecked(match memchr::memchr(b'\n', &line[column0..]) {
None => line,
Some(pos) => &line[..column0 + pos],
})
}
}
#[inline]
pub fn fragment<'a>(&self, source: &'a str) -> &'a str {
&source[self.start.offset()..self.end.offset()]
}
fn before<'a>(&self, source: &'a str) -> (usize, &'a [u8]) {
let before_self = source[..self.offset()].as_bytes();
let offset = match memchr::memrchr(b'\n', before_self) {
None => self.start.offset() + 1,
Some(pos) => self.start.offset() - pos,
};
(offset, &before_self[self.start.offset() - (offset - 1)..])
}
#[inline]
pub const fn len(&self) -> usize {
self.end.offset() - self.start.offset()
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_empty() {
write!(f, "{}:{}", self.start.line(), self.start.column(),)
} else {
write!(
f,
"{}:{}..{}:{}",
self.start.line(),
self.start.column(),
self.end.line(),
self.end.column(),
)
}
}
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_empty() {
write!(
f,
"[{}] {}:{}",
self.start.offset(),
self.start.line(),
self.start.column(),
)
} else {
write!(
f,
"[{}..{}] {}:{}..{}:{}",
self.start.offset(),
self.end.offset(),
self.start.line(),
self.start.column(),
self.end.line(),
self.end.column(),
)
}
}
}
impl Ord for Span {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.start.cmp(&other.start)
}
}
impl PartialOrd for Span {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Position> for Span {
#[inline]
fn from(pos: Position) -> Self {
Self::new_at(pos)
}
}
impl From<Input<'_>> for Span {
#[inline]
fn from(input: Input<'_>) -> Self {
Self::new_at(input.position())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create() {
let start = Position::new(0, 1, 1);
let end = Position::new(2, 1, 3);
let span = Span::new(start, end);
assert_eq!(span.start(), start);
assert_eq!(span.end(), end);
let pos = Position::new(0, 1, 1);
let span = Span::new_at(pos);
assert_eq!(span.start(), pos);
assert_eq!(span.end(), pos);
}
#[test]
fn line_str() {
let pos = Position::new(0, 1, 1);
let span = Span::new_at(pos);
assert_eq!(span.line_str("abc"), "abc");
let pos = Position::new(3, 1, 4);
let span = Span::new_at(pos);
assert_eq!(span.line_str("abc"), "abc");
let pos = Position::new(5, 2, 2);
let span = Span::new_at(pos);
assert_eq!(span.line_str("abc\ndef"), "def");
}
#[test]
fn fragment() {
let start = Position::new(0, 1, 1);
let end = Position::new(2, 1, 3);
let span = Span::new(start, end);
assert_eq!(span.fragment("abc"), "ab");
let start = Position::new(0, 1, 1);
let end = Position::new(3, 1, 4);
let span = Span::new(start, end);
assert_eq!(span.fragment("abc"), "abc");
let start = Position::new(0, 1, 1);
let end = Position::new(5, 2, 2);
let span = Span::new(start, end);
assert_eq!(span.fragment("abc\ndef"), "abc\nd");
}
#[test]
fn length() {
let pos = Position::new(0, 1, 1);
let span = Span::new_at(pos);
assert_eq!(span.len(), 0);
let start = Position::new(0, 1, 1);
let end = Position::new(2, 1, 3);
let span = Span::new(start, end);
assert_eq!(span.len(), 2);
let start = Position::new(0, 1, 1);
let end = Position::new(3, 1, 4);
let span = Span::new(start, end);
assert_eq!(span.len(), 3);
let start = Position::new(0, 1, 1);
let end = Position::new(5, 2, 2);
let span = Span::new(start, end);
assert_eq!(span.len(), 5);
}
}