1extern crate alloc;
2
3use {
4 crate::{nonzero, utils::PathLike, FullParsingError},
5 alloc::borrow::Cow,
6 core::{
7 char::REPLACEMENT_CHARACTER,
8 fmt::{Display, Formatter, Write},
9 num::NonZero,
10 },
11 std::{convert::Infallible, fs::read_to_string, path::Path},
12};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct Location {
17 pub line: NonZero<u32>,
19 pub col: u32,
21}
22
23#[cfg(feature = "proc-macro2")]
25#[derive(Debug, Clone, Copy)]
26pub enum LineColumnToLocationError {
27 LineZero,
29 LineNumberTooBig,
31 ColumnNumberTooBig,
33}
34
35#[cfg(feature = "proc-macro2")]
36impl Display for LineColumnToLocationError {
37 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
38 f.write_str(match self {
39 Self::LineZero => "`LineColumn` with line #0 found",
40 Self::LineNumberTooBig => "`LineColumn`s line number overflowed a u32",
41 Self::ColumnNumberTooBig => "`LineColumn`s column number overflowed a u32",
42 })
43 }
44}
45
46#[cfg(feature = "proc-macro2")]
47impl TryFrom<proc_macro2::LineColumn> for Location {
48 type Error = LineColumnToLocationError;
49
50 fn try_from(value: proc_macro2::LineColumn) -> Result<Self, Self::Error> {
51 let line =
52 u32::try_from(value.line).map_err(|_| LineColumnToLocationError::LineNumberTooBig)?;
53 let line = NonZero::new(line).ok_or(LineColumnToLocationError::LineZero)?;
54 let col = u32::try_from(value.column)
55 .map_err(|_| LineColumnToLocationError::ColumnNumberTooBig)?;
56
57 Ok(Self { line, col })
58 }
59}
60
61#[cfg(feature = "proc-macro2")]
62impl From<Location> for proc_macro2::LineColumn {
63 fn from(value: Location) -> Self {
64 Self {
65 line: value.line.get() as usize,
66 column: value.col as usize,
67 }
68 }
69}
70
71impl Default for Location {
72 fn default() -> Self {
73 Self {
74 line: nonzero!(1),
75 col: 0,
76 }
77 }
78}
79
80impl Display for Location {
81 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
82 write!(f, "{}:{}", self.line, self.col)
83 }
84}
85
86impl Location {
87 #[must_use]
93 pub const fn offset(self, rhs: Self) -> Self {
94 if rhs.line.get() == 1 {
95 Self {
96 line: self.line,
97 col: self.col + rhs.col,
98 }
99 } else {
100 Self {
101 line: NonZero::new(self.line.get() + rhs.line.get() - 1).expect("no overflow"),
102 col: rhs.col,
103 }
104 }
105 }
106
107 pub fn with_path<'path>(self, path: impl PathLike<'path>) -> FullLocation<'path> {
109 FullLocation {
110 path: path.into_path_bytes(),
111 loc: self,
112 }
113 }
114
115 pub fn find(ptr: *const u8, src: &str) -> Option<Self> {
118 let progress =
119 usize::checked_sub(ptr as _, src.as_ptr() as _).filter(|x| *x <= src.len())?;
120
121 Some(
122 src.bytes()
123 .take(progress)
124 .fold(Self::default(), |loc, b| match b {
125 b'\n' => Self {
126 line: loc.line.saturating_add(1),
127 col: 0,
128 },
129 _ => Self {
130 col: loc.col.saturating_add(1),
131 ..loc
132 },
133 }),
134 )
135 }
136
137 pub fn find_saturating(ptr: *const u8, src: &str) -> Self {
143 let progress = usize::saturating_sub(ptr as _, src.as_ptr() as _);
144
145 let res = src
146 .bytes()
147 .take(progress)
148 .fold(Self::default(), |loc, b| match b {
149 b'\n' => Self {
150 line: loc.line.saturating_add(1),
151 col: 0,
152 },
153 _ => Self {
154 col: loc.col.saturating_add(1),
155 ..loc
156 },
157 });
158 res
159 }
160
161 pub fn find_in_multiple<K>(
186 ptr: *const u8,
187 files: impl IntoIterator<Item = (K, impl AsRef<str>)>,
188 ) -> Option<(K, Self)> {
189 files
190 .into_iter()
191 .find_map(|(k, src)| Some((k, Self::find(ptr, src.as_ref())?)))
192 }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Hash)]
197pub struct FullLocation<'path> {
198 path: Cow<'path, [u8]>,
200 pub loc: Location,
202}
203
204impl Display for FullLocation<'_> {
205 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
206 for chunk in self.path.utf8_chunks() {
207 f.write_str(chunk.valid())?;
208 if !chunk.invalid().is_empty() {
209 f.write_char(REPLACEMENT_CHARACTER)?;
210 }
211 }
212 write!(f, ":{}", self.loc)
213 }
214}
215
216impl<'path> FullLocation<'path> {
217 pub fn path(&self) -> &Path {
219 #[cfg(unix)]
220 let res = <std::ffi::OsStr as std::os::unix::ffi::OsStrExt>::from_bytes(&self.path);
221
222 #[cfg(not(unix))]
223 let res = str::from_utf8(&self.path).expect("UTF-8 path when calling FullLocation::path");
224
225 Path::new(res)
226 }
227
228 pub fn with_source_line(&self) -> impl Display + '_ {
231 let src = read_to_string(self.path()).unwrap_or_default();
232
233 FullParsingError::<Infallible> {
234 loc: FullLocation {
235 path: Cow::Borrowed(&self.path),
236 loc: self.loc,
237 },
238 reason: None,
239 src: src.into(),
240 }
241 }
242
243 pub fn own(self) -> FullLocation<'static> {
245 FullLocation {
246 path: self.path.into_owned().into(),
247 loc: self.loc,
248 }
249 }
250}