#![warn(clippy::perf, clippy::must_use_candidate)]
use std::cmp::{Ord, Ordering, PartialOrd};
mod buffer;
pub mod fmt;
mod loc;
mod metrics;
mod position;
mod layout;
pub use buffer::SourceBuffer;
pub use loc::Loc;
pub use metrics::*;
pub use position::Position;
pub use layout::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Span {
start: Position,
last: Position,
end: Position,
}
impl PartialOrd for Span {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for Span {
fn cmp(&self, other: &Self) -> Ordering {
if self == other {
Ordering::Equal
} else if self.includes(other) {
Ordering::Greater
} else if other.includes(self) {
Ordering::Less
} else {
self.start.cmp(&other.start)
}
}
}
impl Span {
#[must_use]
pub fn new(start: Position, mut last: Position, mut end: Position) -> Self {
if end < start || last < start {
last = start;
end = start;
}
if last >= end && end != start {
panic!("invalid span ({:?}, {:?}, {:?})", start, last, end);
}
Self { start, last, end }
}
pub fn of_string<M: Metrics>(str: &str, metrics: &M) -> Self {
let mut last = Position::new(0, 0);
let mut end = Position::new(0, 0);
for c in str.chars() {
last = end;
end.shift(c, metrics)
}
Self {
start: Position::new(0, 0),
last,
end,
}
}
#[must_use]
pub const fn start(&self) -> Position { self.start }
#[must_use]
pub const fn last(&self) -> Position { self.last }
#[must_use]
pub const fn end(&self) -> Position { self.end }
#[must_use]
pub fn is_empty(&self) -> bool { self.start == self.end }
#[must_use]
pub fn overlaps(&self, other: &Span) -> bool {
(self.start <= other.start && self.end > other.start)
|| (other.start <= self.start && other.end > self.start)
}
#[must_use]
pub fn includes(&self, other: &Span) -> bool {
self.start <= other.start && self.last >= other.last
}
#[must_use]
pub const fn line_count(&self) -> usize { self.last.line - self.start.line + 1 }
#[must_use]
pub fn includes_line(&self, line: usize) -> bool {
line >= self.start.line && line <= self.end.line
}
pub fn push_column(&mut self) {
self.last = self.end;
self.end = self.end.next_column();
}
pub fn push_line(&mut self) {
self.last = self.end;
self.end = self.end.next_line();
}
pub fn push<M: Metrics>(&mut self, c: char, metrics: &M) {
self.last = self.end;
self.end = self.end.next(c, metrics);
}
#[must_use]
pub fn union(&self, other: Self) -> Self {
if other.last > self.last && other.end > self.end {
Self {
start: std::cmp::min(self.start, other.start),
last: other.last,
end: other.end,
}
} else {
Self {
start: std::cmp::min(self.start, other.start),
last: self.last,
end: self.end,
}
}
}
#[must_use]
pub fn inter(&self, other: Self) -> Self {
let start = std::cmp::max(self.start, other.start);
Self::new(start, other.last, other.end)
}
pub fn append(&mut self, other: Self) {
if other.last > self.last && other.end > self.end {
self.last = other.last;
self.end = other.end;
}
}
#[must_use]
pub const fn next(&self) -> Self {
Self {
start: self.end,
last: self.end,
end: self.end,
}
}
pub fn clear(&mut self) {
self.start = self.end;
self.last = self.end;
}
#[must_use]
pub const fn aligned(&self) -> Self {
Self {
start: Position {
line: self.start.line,
column: 0,
},
last: Position {
line: self.end.line,
column: usize::max_value() - 1,
},
end: Position {
line: self.end.line,
column: usize::max_value(),
},
}
}
}
impl From<Position> for Span {
fn from(pos: Position) -> Self {
Self {
start: pos,
last: pos,
end: pos,
}
}
}
impl ::std::fmt::Display for Span {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
if self.start == self.last {
write!(f, "{}", self.start)
} else {
write!(f, "from {:?} to {:?}", self.start, self.end)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_display_span() {
assert_eq!(
Span::new(
Position::new(0, 0),
Position::new(1, 20),
Position::new(3, 41),
)
.to_string(),
"from 1:1 to 4:42".to_string()
);
}
}