use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::num::NonZeroUsize;
use std::ops::{Add, Range, Sub};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
macro_rules! impl_pos {
($pos_type:ident, $primitive:ty) => {
impl Add for $pos_type {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl Add<$primitive> for $pos_type {
type Output = Self;
fn add(self, rhs: $primitive) -> Self::Output {
Self(self.0 + rhs)
}
}
impl Sub for $pos_type {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl Sub<$primitive> for $pos_type {
type Output = Self;
fn sub(self, rhs: $primitive) -> Self::Output {
Self(self.0 - rhs)
}
}
impl $pos_type {
#[inline(always)]
#[must_use]
pub fn from_usize(n: usize) -> Self {
Self(n as $primitive)
}
#[inline(always)]
#[must_use]
pub fn to_usize(&self) -> usize {
self.0 as usize
}
}
impl From<usize> for $pos_type {
fn from(n: usize) -> Self {
Self::from_usize(n)
}
}
};
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ByteOffset(pub u32);
impl_pos!(ByteOffset, u32);
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LineOffset(pub u32);
impl_pos!(LineOffset, u32);
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CharOffset(pub u32);
impl_pos!(CharOffset, u32);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BytePosition(pub ByteOffset);
impl From<ByteOffset> for BytePosition {
fn from(offset: ByteOffset) -> Self {
Self(offset)
}
}
impl From<usize> for BytePosition {
fn from(offset: usize) -> Self {
Self(offset.into())
}
}
impl fmt::Display for BytePosition {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let BytePosition(ByteOffset(n)) = self;
write!(f, "b{n}")
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LineAndCharPosition {
pub line: LineOffset,
pub char: CharOffset,
}
impl LineAndCharPosition {
#[inline]
#[must_use]
pub fn new(line: usize, char: usize) -> Self {
Self {
line: LineOffset::from_usize(line),
char: CharOffset::from_usize(char),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LineAndColumn {
pub line: NonZeroUsize,
pub column: NonZeroUsize,
}
impl LineAndColumn {
#[inline]
#[must_use]
pub fn new(line: usize, column: usize) -> Option<Self> {
Some(Self {
line: NonZeroUsize::new(line)?,
column: NonZeroUsize::new(column)?,
})
}
#[inline]
#[must_use]
pub const unsafe fn new_unchecked(line: usize, column: usize) -> Self {
Self {
line: NonZeroUsize::new_unchecked(line),
column: NonZeroUsize::new_unchecked(column),
}
}
}
impl From<LineAndCharPosition> for LineAndColumn {
fn from(LineAndCharPosition { line, char }: LineAndCharPosition) -> Self {
let line = line.to_usize() + 1;
let column = char.to_usize() + 1;
unsafe { LineAndColumn::new_unchecked(line, column) }
}
}
impl fmt::Display for LineAndColumn {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Location<Loc: Display> {
pub start: Loc,
pub end: Loc,
}
impl<Loc> fmt::Display for Location<Loc>
where
Loc: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "(")?;
self.start.fmt(f)?;
write!(f, "..")?;
self.end.fmt(f)?;
write!(f, ")")?;
Ok(())
}
}
impl<Loc> From<Range<Loc>> for Location<Loc>
where
Loc: Display,
{
fn from(Range { start, end }: Range<Loc>) -> Self {
Location { start, end }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Located<T, Loc: Display> {
pub inner: T,
pub location: Location<Loc>,
}
pub trait ToLocated<Loc: Display>: Sized {
fn to_located<IntoLoc>(self, location: IntoLoc) -> Located<Self, Loc>
where
IntoLoc: Into<Location<Loc>>,
{
Located {
inner: self,
location: location.into(),
}
}
}
impl<T, Loc: Display> ToLocated<Loc> for T {}
impl<T, Loc: Display> Located<T, Loc> {
pub fn map_loc<F, Loc2>(self, mut tx: F) -> Located<T, Loc2>
where
Loc2: Display,
F: FnMut(Loc) -> Loc2,
{
let Located { inner, location } = self;
let location = Range {
start: tx(location.start),
end: tx(location.end),
};
inner.to_located(location)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::num::NonZeroUsize;
use crate::syntax::location::{ByteOffset, BytePosition, Located, Location};
#[test]
fn located() {
let l1: Located<String, BytePosition> = "test"
.to_string()
.to_located(ByteOffset(0).into()..ByteOffset(42).into());
assert_eq!(l1.inner, "test");
assert_eq!(l1.location.start.0 .0, 0);
assert_eq!(l1.location.end.0 .0, 42);
assert_eq!(l1.location.to_string(), "(b0..b42)");
let l1c = l1.clone();
assert!(matches!(
l1c,
Located {
inner: s,
location: Location {
start:BytePosition(ByteOffset(0)),
end: BytePosition(ByteOffset(42))
}
} if s == "test"
));
let l2 = l1.map_loc(|x| x);
assert!(matches!(
l2.location,
Location {
start: BytePosition(ByteOffset(0)),
end: BytePosition(ByteOffset(42))
}
));
}
#[test]
fn byteoff() {
let offset1 = ByteOffset(5);
let offset2 = ByteOffset::from_usize(15);
assert_eq!(20, (offset1 + offset2).to_usize());
assert_eq!(10, (offset2 - offset1).to_usize());
assert_eq!(ByteOffset(10), offset2 - 5);
assert_eq!(ByteOffset(20), offset2 + 5);
}
#[test]
fn lineoff() {
let offset1 = LineOffset(5);
let offset2 = LineOffset::from_usize(15);
assert_eq!(20, (offset1 + offset2).to_usize());
assert_eq!(10, (offset2 - offset1).to_usize());
assert_eq!(LineOffset(10), offset2 - 5);
assert_eq!(LineOffset(20), offset2 + 5);
}
#[test]
fn charoff() {
let offset1 = CharOffset(5);
let offset2 = CharOffset::from_usize(15);
assert_eq!(20, (offset1 + offset2).to_usize());
assert_eq!(10, (offset2 - offset1).to_usize());
assert_eq!(CharOffset(10), offset2 - 5);
assert_eq!(CharOffset(20), offset2 + 5);
}
#[test]
fn positions() {
assert_eq!(BytePosition(ByteOffset(15)), BytePosition(15.into()));
assert_eq!(BytePosition(ByteOffset(5)), ByteOffset(5).into());
assert_eq!(BytePosition(ByteOffset(25)), 25.into());
assert_eq!("b25", format!("{}", BytePosition(ByteOffset(25))));
assert_eq!("b25", BytePosition(ByteOffset(25)).to_string());
let loc = LineAndCharPosition::new(13, 42);
assert_eq!(
LineAndCharPosition {
line: LineOffset(13),
char: CharOffset(42)
},
loc
);
let display = LineAndColumn {
line: unsafe { NonZeroUsize::new_unchecked(14) },
column: unsafe { NonZeroUsize::new_unchecked(43) },
};
assert_eq!(display, loc.into());
assert_eq!(display, unsafe { LineAndColumn::new_unchecked(14, 43) });
assert_eq!(display, LineAndColumn::new(14, 43).unwrap());
assert_eq!("14:43", format!("{display}"));
assert_eq!("14:43", display.to_string());
}
}