1extern crate alloc;
2
3use {
4 crate::{nonzero, utils::PathLike},
5 alloc::borrow::Cow,
6 core::{
7 char::REPLACEMENT_CHARACTER,
8 fmt::{Display, Formatter, Write},
9 num::NonZero,
10 },
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct Location {
16 pub line: NonZero<u32>,
18 pub col: u32,
20}
21
22#[cfg(feature = "proc-macro2")]
24#[derive(Debug, Clone, Copy)]
25pub enum LineColumnToLocationError {
26 LineZero,
28 LineNumberTooBig,
30 ColumnNumberTooBig,
32}
33
34#[cfg(feature = "proc-macro2")]
35impl Display for LineColumnToLocationError {
36 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
37 f.write_str(match self {
38 Self::LineZero => "`LineColumn` with line #0 found",
39 Self::LineNumberTooBig => "`LineColumn`s line number overflowed a u32",
40 Self::ColumnNumberTooBig => "`LineColumn`s column number overflowed a u32",
41 })
42 }
43}
44
45#[cfg(feature = "proc-macro2")]
46impl TryFrom<proc_macro2::LineColumn> for Location {
47 type Error = LineColumnToLocationError;
48
49 fn try_from(value: proc_macro2::LineColumn) -> Result<Self, Self::Error> {
50 let line = u32::try_from(value.line).map_err(|_| LineColumnToLocationError::LineNumberTooBig)?;
51 let line = NonZero::new(line).ok_or(LineColumnToLocationError::LineZero)?;
52 let col = u32::try_from(value.column).map_err(|_| LineColumnToLocationError::ColumnNumberTooBig)?;
53
54 Ok(Self { line, col })
55 }
56}
57
58impl Default for Location {
59 fn default() -> Self {
60 Self {
61 line: nonzero!(1),
62 col: 0,
63 }
64 }
65}
66
67impl Display for Location {
68 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
69 write!(f, "{}:{}", self.line, self.col)
70 }
71}
72
73impl Location {
74 #[must_use]
80 pub const fn offset(self, rhs: Self) -> Self {
81 if rhs.line.get() == 1 {
82 Self {
83 line: self.line,
84 col: self.col - rhs.col,
85 }
86 } else {
87 Self {
88 line: NonZero::new(self.line.get() - rhs.line.get() + 1).expect("no overflow"),
89 col: rhs.col,
90 }
91 }
92 }
93
94 pub fn with_path<'path>(self, path: impl PathLike<'path>) -> FullLocation<'path> {
96 FullLocation {
97 path: path.into_path_bytes(),
98 loc: self,
99 }
100 }
101
102 pub fn find(ptr: *const u8, src: &str) -> Option<Self> {
105 let progress =
106 usize::checked_sub(ptr as _, src.as_ptr() as _).filter(|x| *x <= src.len())?;
107
108 Some(
109 src.bytes()
110 .take(progress)
111 .fold(Self::default(), |loc, b| match b {
112 b'\n' => Self {
113 line: loc.line.saturating_add(1),
114 col: 0,
115 },
116 _ => Self {
117 col: loc.col.saturating_add(1),
118 ..loc
119 },
120 }),
121 )
122 }
123
124 pub fn find_saturating(ptr: *const u8, src: &str) -> Self {
130 let progress = usize::saturating_sub(ptr as _, src.as_ptr() as _);
131
132 let res = src
133 .bytes()
134 .take(progress)
135 .fold(Self::default(), |loc, b| match b {
136 b'\n' => Self {
137 line: loc.line.saturating_add(1),
138 col: 0,
139 },
140 _ => Self {
141 col: loc.col.saturating_add(1),
142 ..loc
143 },
144 });
145 res
146 }
147
148 pub fn find_in_multiple<K>(
173 ptr: *const u8,
174 files: impl IntoIterator<Item = (K, impl AsRef<str>)>,
175 ) -> Option<(K, Self)> {
176 files
177 .into_iter()
178 .find_map(|(k, src)| Some((k, Self::find(ptr, src.as_ref())?)))
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, Eq, Hash)]
184pub struct FullLocation<'path> {
185 pub path: Cow<'path, [u8]>,
187 pub loc: Location,
189}
190
191impl Display for FullLocation<'_> {
192 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
193 for chunk in self.path.utf8_chunks() {
194 f.write_str(chunk.valid())?;
195 if !chunk.invalid().is_empty() {
196 f.write_char(REPLACEMENT_CHARACTER)?;
197 }
198 }
199 write!(f, ":{}", self.loc)
200 }
201}
202
203impl FullLocation<'_> {
204 pub fn own(self) -> FullLocation<'static> {
206 FullLocation {
207 path: self.path.into_owned().into(),
208 loc: self.loc,
209 }
210 }
211}