use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct Position {
pub byte_offset: u64,
pub line: u64,
pub column: u64,
}
impl Position {
pub const START: Self = Self {
byte_offset: 0,
line: 1,
column: 1,
};
#[must_use]
pub const fn new(byte_offset: u64, line: u64, column: u64) -> Self {
Self {
byte_offset,
line,
column,
}
}
#[must_use]
pub const fn advance(self, byte: u8) -> Option<Self> {
let Some(byte_offset) = self.byte_offset.checked_add(1) else {
return None;
};
if byte == b'\n' {
let Some(line) = self.line.checked_add(1) else {
return None;
};
Some(Self {
byte_offset,
line,
column: 1,
})
} else {
let Some(column) = self.column.checked_add(1) else {
return None;
};
Some(Self {
byte_offset,
line: self.line,
column,
})
}
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct ByteSpan {
pub start: Position,
pub end: u64,
}
impl ByteSpan {
#[must_use]
pub const fn new(start: Position, end: u64) -> Self {
Self { start, end }
}
#[must_use]
pub const fn empty(at: Position) -> Self {
Self {
start: at,
end: at.byte_offset,
}
}
#[must_use]
pub const fn len(self) -> Option<u64> {
self.end.checked_sub(self.start.byte_offset)
}
#[must_use]
pub const fn is_empty(self) -> bool {
matches!(self.len(), Some(0))
}
#[must_use]
pub fn slice(self, source: &[u8]) -> Option<&[u8]> {
let start = usize::try_from(self.start.byte_offset).ok()?;
let end = usize::try_from(self.end).ok()?;
source.get(start..end)
}
}
impl fmt::Display for ByteSpan {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.start)
}
}
#[cfg(test)]
#[path = "location_tests.rs"]
mod tests;