1use serde::{Deserialize, Serialize};
2use spade_codespan::{ByteOffset, Span};
3use spade_codespan_reporting::diagnostic::Label;
4
5pub trait AsLabel {
6 fn file_id(&self) -> usize;
7 fn span(&self) -> std::ops::Range<usize>;
8
9 fn primary_label(&self) -> Label<usize> {
10 Label::primary(self.file_id(), self.span())
11 }
12
13 fn secondary_label(&self) -> Label<usize> {
14 Label::secondary(self.file_id(), self.span())
15 }
16}
17
18pub type FullSpan = (Span, usize);
19
20impl<T> From<&Loc<T>> for FullSpan {
21 fn from(loc: &Loc<T>) -> Self {
22 (loc.span, loc.file_id)
23 }
24}
25
26impl<T> From<Loc<T>> for FullSpan {
27 fn from(loc: Loc<T>) -> Self {
28 FullSpan::from(&loc)
29 }
30}
31
32impl AsLabel for FullSpan {
33 fn span(&self) -> std::ops::Range<usize> {
34 self.0.into()
35 }
36
37 fn file_id(&self) -> usize {
38 self.1
39 }
40}
41
42pub trait HasCodespan {
43 fn codespan(&self) -> Span;
44}
45
46impl<T> HasCodespan for Loc<T> {
47 fn codespan(&self) -> Span {
48 self.span
49 }
50}
51
52impl HasCodespan for Span {
53 fn codespan(&self) -> Span {
54 *self
55 }
56}
57
58impl HasCodespan for std::ops::Range<usize> {
59 fn codespan(&self) -> Span {
60 lspan(self.clone())
61 }
62}
63
64pub trait WithLocation: Sized {
65 fn at(self, file_id: usize, span: &impl HasCodespan) -> Loc<Self>
66 where
67 Self: Sized,
68 {
69 Loc::new(self, span.codespan(), file_id)
70 }
71
72 fn at_loc<T: Sized>(self, loc: &Loc<T>) -> Loc<Self> {
74 Loc::new(self, loc.span, loc.file_id)
75 }
76
77 fn near_loc<T: Sized>(self, loc: &Loc<T>) -> Loc<Self> {
81 Loc::new(self, loc.span, loc.file_id)
82 }
83
84 fn between(
85 self,
86 file_id: usize,
87 start: &impl HasCodespan,
88 end: &impl HasCodespan,
89 ) -> Loc<Self> {
90 Loc::new(self, start.codespan().merge(end.codespan()), file_id)
91 }
92
93 fn between_locs<T, Y>(self, start: &Loc<T>, end: &Loc<Y>) -> Loc<Self> {
94 assert!(start.file_id == end.file_id);
95 Loc::new(self, start.codespan().merge(end.codespan()), end.file_id())
96 }
97
98 fn nowhere(self) -> Loc<Self>
99 where
100 Self: Sized,
101 {
102 self.at(0, &Span::new(0, 0))
103 }
104}
105
106impl<T> WithLocation for T {}
107
108pub fn lspan(s: logos::Span) -> Span {
109 Span::new(s.start as u32, s.end as u32)
110}
111
112#[cfg(test)]
113pub fn dummy() -> Span {
114 Span::new(0, 0)
115}
116
117#[derive(Clone, Copy, Serialize, Deserialize)]
118pub struct Loc<T> {
119 pub inner: T,
120 pub span: Span,
121 pub file_id: usize,
122}
123
124impl<T> Loc<T> {
125 #[inline(always)]
126 pub fn new(inner: T, span: Span, file_id: usize) -> Self {
127 Self {
128 inner,
129 span,
130 file_id,
131 }
132 }
133 #[inline(always)]
134 pub fn nowhere(inner: T) -> Self {
135 Self::new(inner, Span::new(0, 0), 0)
136 }
137
138 #[inline(always)]
139 pub fn strip(self) -> T {
140 self.inner
141 }
142
143 #[inline(always)]
144 pub fn strip_ref(&self) -> &T {
145 &self.inner
146 }
147
148 #[inline(always)]
149 pub fn separate(self) -> (Self, Span) {
150 let span = self.span;
151 (self, span)
152 }
153
154 #[inline(always)]
155 pub fn separate_loc(self) -> (Self, Loc<()>) {
156 let loc = self.loc();
157 (self, loc)
158 }
159
160 #[inline(always)]
161 pub fn split(self) -> (T, Span) {
162 (self.inner, self.span)
163 }
164 #[inline(always)]
165 pub fn split_ref(&self) -> (&T, Span) {
166 (&self.inner, self.span)
167 }
168 #[inline(always)]
169 pub fn split_loc(self) -> (T, Loc<()>) {
170 let loc = self.loc();
171 (self.inner, loc)
172 }
173 #[inline(always)]
174 pub fn split_loc_ref(&self) -> (&T, Loc<()>) {
175 let loc = self.loc();
176 (&self.inner, loc)
177 }
178
179 pub fn is_same_loc<R>(&self, other: &Loc<R>) -> bool {
180 self.span == other.span && self.file_id == other.file_id
181 }
182
183 #[inline(always)]
184 pub fn map<Y>(self, mut op: impl FnMut(T) -> Y) -> Loc<Y> {
185 Loc {
186 inner: op(self.inner),
187 span: self.span,
188 file_id: self.file_id,
189 }
190 }
191
192 #[inline(always)]
193 pub fn try_map<Y, E>(self, mut op: impl FnMut(T) -> Result<Y, E>) -> Result<Loc<Y>, E> {
194 Ok(Loc {
195 inner: op(self.inner)?,
196 span: self.span,
197 file_id: self.file_id,
198 })
199 }
200
201 #[inline(always)]
202 pub fn map_ref<Y>(&self, mut op: impl FnMut(&T) -> Y) -> Loc<Y> {
203 Loc {
204 inner: op(&self.inner),
205 span: self.span,
206 file_id: self.file_id,
207 }
208 }
209
210 #[inline(always)]
211 pub fn try_map_ref<Y, E>(&self, mut op: impl FnMut(&T) -> Result<Y, E>) -> Result<Loc<Y>, E> {
212 Ok(Loc {
213 inner: op(&self.inner)?,
214 span: self.span,
215 file_id: self.file_id,
216 })
217 }
218
219 #[inline(always)]
220 pub fn loc(&self) -> Loc<()> {
221 Loc {
222 inner: (),
223 span: self.span,
224 file_id: self.file_id,
225 }
226 }
227
228 pub fn contains_start<R>(&self, other: &Loc<R>) -> bool {
229 other.file_id == self.file_id
230 && other.span.start() >= self.span.start()
231 && other.span.start() < self.span.end()
232 }
233
234 pub fn ends_before<R>(&self, other: &Loc<R>) -> bool {
236 other.file_id == self.file_id && self.span.end() < other.span.start()
237 }
238
239 pub fn start_span(&self) -> Loc<()> {
240 ().at(
241 self.file_id,
242 &Span::new(self.span.start(), self.span.start()),
243 )
244 }
245 pub fn end_span(&self) -> Loc<()> {
246 ().at(self.file_id, &Span::new(self.span.end(), self.span.end()))
247 }
248
249 pub fn shrink_left(&self, s: &str) -> Loc<()> {
261 if self.span.start() == self.span.end() {
262 self.loc()
263 } else {
264 Loc {
265 inner: (),
266 span: Span::new(
267 self.span.start() + ByteOffset::from_str_len(s),
268 self.span.end(),
269 ),
270 file_id: self.file_id,
271 }
272 }
273 }
274
275 pub fn shrink_right(&self, s: &str) -> Loc<()> {
277 if self.span.start() == self.span.end() {
278 self.loc()
279 } else {
280 Loc {
281 inner: (),
282 span: Span::new(
283 self.span.start(),
284 self.span.end() - ByteOffset::from_str_len(s),
285 ),
286 file_id: self.file_id,
287 }
288 }
289 }
290}
291
292impl<T, E> Loc<Result<T, E>> {
293 pub fn map_err<E2>(self, err_fn: impl Fn(E, Loc<()>) -> E2) -> Result<Loc<T>, E2> {
294 match self.inner {
295 Ok(inner) => Ok(Loc {
296 inner,
297 span: self.span,
298 file_id: self.file_id,
299 }),
300 Err(e) => Err(err_fn(e, ().at(self.file_id, &self.span))),
301 }
302 }
303}
304
305impl<T> Loc<Option<T>>
306where
307 T: WithLocation,
308{
309 pub fn transpose(self) -> Option<Loc<T>> {
310 let loc = self.loc();
311 match self.inner {
312 Some(inner) => Some(inner.at_loc(&loc)),
313 None => None,
314 }
315 }
316}
317
318impl<T> PartialEq for Loc<T>
319where
320 T: PartialEq,
321{
322 #[inline(always)]
323 fn eq(&self, other: &Self) -> bool {
324 self.inner == other.inner
325 }
326}
327
328impl<T> Eq for Loc<T> where T: Eq {}
329
330impl<T> PartialOrd for Loc<T>
331where
332 T: PartialOrd,
333{
334 #[inline(always)]
335 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
336 self.inner.partial_cmp(&other.inner)
337 }
338}
339
340impl<T> Ord for Loc<T>
341where
342 T: Ord,
343{
344 #[inline(always)]
345 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
346 self.inner.cmp(&other.inner)
347 }
348}
349
350impl<T> std::fmt::Display for Loc<T>
351where
352 T: std::fmt::Display,
353{
354 #[inline(always)]
355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356 self.inner.fmt(f)
357 }
358}
359
360impl<T> std::hash::Hash for Loc<T>
361where
362 T: std::hash::Hash,
363{
364 #[inline(always)]
365 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
366 self.inner.hash(state)
367 }
368}
369
370impl<T> std::fmt::Debug for Loc<T>
371where
372 T: std::fmt::Debug,
373{
374 #[inline(always)]
375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376 self.inner.fmt(f)
377 }
378}
379
380impl<T> std::ops::Deref for Loc<T> {
381 type Target = T;
382
383 #[inline(always)]
384 fn deref(&self) -> &Self::Target {
385 &self.inner
386 }
387}
388impl<T> std::ops::DerefMut for Loc<T> {
389 #[inline(always)]
390 fn deref_mut(&mut self) -> &mut Self::Target {
391 &mut self.inner
392 }
393}
394
395impl<T> AsLabel for Loc<T> {
396 #[inline(always)]
397 fn file_id(&self) -> usize {
398 self.file_id
399 }
400
401 #[inline(always)]
402 fn span(&self) -> std::ops::Range<usize> {
403 self.span.into()
404 }
405}