use std::{fmt, hash};
use crate::parser;
#[derive(Clone, Copy, Debug, Eq)]
pub struct Position {
pub(super) offset: u32,
pub(super) line: u16,
pub(super) column: u16,
}
impl Position {
#[allow(clippy::cast_possible_truncation)]
#[must_use]
pub fn new(offset: usize, line: usize, column: usize) -> Self {
let line = line.clamp(1, u16::MAX as usize);
let column = column.clamp(1, u16::MAX as usize);
let offset = u32::try_from(offset).map_or(u32::MAX, |offset| offset);
Self {
offset,
line: line as u16,
column: column as u16,
}
}
#[must_use]
pub const fn rebase(&self, base: Self) -> Self {
let offset = base.offset + self.offset;
let line = base.line + self.line - 1;
let column = if self.line == 1 {
base.column + self.column - 1
} else {
self.column
};
Self {
offset,
line,
column,
}
}
#[must_use]
#[inline]
pub const fn offset(&self) -> usize {
self.offset as usize
}
#[must_use]
#[inline]
pub const fn line(&self) -> usize {
self.line as usize
}
#[must_use]
#[inline]
pub const fn column(&self) -> usize {
self.column as usize
}
}
impl Default for Position {
fn default() -> Self {
Self {
offset: 0,
line: 1,
column: 1,
}
}
}
impl PartialEq for Position {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.offset == other.offset
}
}
impl Ord for Position {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.offset.cmp(&other.offset)
}
}
impl PartialOrd for Position {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl hash::Hash for Position {
#[inline]
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.offset.hash(state);
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "{}:{}:{}", self.offset, self.line, self.column)
} else {
write!(f, "{}:{}", self.line, self.column)
}
}
}
impl From<parser::Input<'_>> for Position {
#[inline]
fn from(input: parser::Input<'_>) -> Self {
input.position()
}
}
impl From<&parser::Input<'_>> for Position {
#[inline]
fn from(input: &parser::Input<'_>) -> Self {
input.position()
}
}
impl From<&mut parser::Input<'_>> for Position {
#[inline]
fn from(input: &mut parser::Input<'_>) -> Self {
input.position()
}
}
impl From<&&mut parser::Input<'_>> for Position {
#[inline]
fn from(input: &&mut parser::Input<'_>) -> Self {
input.position()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn position_new() {
let pos = Position::new(0, 1, 1);
assert_eq!(pos.offset(), 0);
assert_eq!(pos.line(), 1);
assert_eq!(pos.column(), 1);
}
#[test]
fn position_rebase() {
let base = Position::new(0, 1, 1);
let pos = Position::new(1, 1, 2);
let pos = pos.rebase(base);
assert_eq!(pos.offset(), 1);
assert_eq!(pos.line(), 1);
assert_eq!(pos.column(), 2);
let base = Position::new(46, 3, 7);
let pos = Position::new(3, 1, 4);
let pos = pos.rebase(base);
assert_eq!(pos.offset(), 49);
assert_eq!(pos.line(), 3);
assert_eq!(pos.column(), 10);
let pos = Position::new(7, 2, 4);
let pos = pos.rebase(base);
assert_eq!(pos.offset(), 53);
assert_eq!(pos.line(), 4);
assert_eq!(pos.column(), 4);
}
#[test]
fn position_display() {
let pos = Position::new(0, 1, 1);
assert_eq!(format!("{pos}"), "1:1");
assert_eq!(format!("{pos:#}"), "0:1:1");
}
}