1#![warn(missing_docs)]
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
13#[repr(transparent)]
14pub struct BytePos(pub u32);
15
16impl BytePos {
17 pub const ZERO: Self = Self(0);
19
20 #[must_use]
22 pub const fn new(pos: u32) -> Self {
23 Self(pos)
24 }
25
26 #[must_use]
28 pub const fn as_u32(self) -> u32 {
29 self.0
30 }
31
32 #[must_use]
34 pub const fn as_usize(self) -> usize {
35 self.0 as usize
36 }
37}
38
39impl std::ops::Add<u32> for BytePos {
40 type Output = Self;
41
42 fn add(self, rhs: u32) -> Self::Output {
43 Self(self.0 + rhs)
44 }
45}
46
47impl std::ops::Sub for BytePos {
48 type Output = u32;
49
50 fn sub(self, rhs: Self) -> Self::Output {
51 self.0 - rhs.0
52 }
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
57pub struct Span {
58 pub lo: BytePos,
60 pub hi: BytePos,
62}
63
64impl Span {
65 pub const DUMMY: Self = Self {
67 lo: BytePos::ZERO,
68 hi: BytePos::ZERO,
69 };
70
71 #[must_use]
73 pub const fn new(lo: BytePos, hi: BytePos) -> Self {
74 Self { lo, hi }
75 }
76
77 #[must_use]
79 pub const fn from_raw(lo: u32, hi: u32) -> Self {
80 Self {
81 lo: BytePos(lo),
82 hi: BytePos(hi),
83 }
84 }
85
86 #[must_use]
88 pub const fn is_dummy(self) -> bool {
89 self.lo.0 == 0 && self.hi.0 == 0
90 }
91
92 #[must_use]
94 pub const fn len(self) -> u32 {
95 self.hi.0 - self.lo.0
96 }
97
98 #[must_use]
100 pub const fn is_empty(self) -> bool {
101 self.lo.0 == self.hi.0
102 }
103
104 #[must_use]
106 pub fn merge(self, other: Self) -> Self {
107 Self {
108 lo: BytePos(self.lo.0.min(other.lo.0)),
109 hi: BytePos(self.hi.0.max(other.hi.0)),
110 }
111 }
112
113 #[must_use]
115 pub const fn to(self, other: Self) -> Self {
116 Self {
117 lo: self.lo,
118 hi: other.hi,
119 }
120 }
121
122 #[must_use]
124 pub const fn shrink_to_lo(self) -> Self {
125 Self {
126 lo: self.lo,
127 hi: self.lo,
128 }
129 }
130
131 #[must_use]
133 pub const fn shrink_to_hi(self) -> Self {
134 Self {
135 lo: self.hi,
136 hi: self.hi,
137 }
138 }
139
140 #[must_use]
142 pub const fn contains(self, pos: BytePos) -> bool {
143 self.lo.0 <= pos.0 && pos.0 < self.hi.0
144 }
145}
146
147impl Default for Span {
148 fn default() -> Self {
149 Self::DUMMY
150 }
151}
152
153#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
155pub struct Spanned<T> {
156 pub node: T,
158 pub span: Span,
160}
161
162impl<T> Spanned<T> {
163 #[must_use]
165 pub const fn new(node: T, span: Span) -> Self {
166 Self { node, span }
167 }
168
169 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Spanned<U> {
171 Spanned {
172 node: f(self.node),
173 span: self.span,
174 }
175 }
176}
177
178#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
180#[repr(transparent)]
181pub struct FileId(pub u32);
182
183impl FileId {
184 #[must_use]
186 pub const fn new(id: u32) -> Self {
187 Self(id)
188 }
189}
190
191#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
193pub struct FullSpan {
194 pub file: FileId,
196 pub span: Span,
198}
199
200impl FullSpan {
201 #[must_use]
203 pub const fn new(file: FileId, span: Span) -> Self {
204 Self { file, span }
205 }
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
210pub struct LineCol {
211 pub line: u32,
213 pub col: u32,
215}
216
217impl LineCol {
218 #[must_use]
220 pub const fn new(line: u32, col: u32) -> Self {
221 Self { line, col }
222 }
223}
224
225#[derive(Clone, Debug)]
227pub struct SourceFile {
228 pub id: FileId,
230 pub name: String,
232 pub src: String,
234 line_starts: Vec<BytePos>,
236}
237
238impl SourceFile {
239 #[must_use]
241 pub fn new(id: FileId, name: String, src: String) -> Self {
242 let line_starts = std::iter::once(BytePos::ZERO)
243 .chain(
244 src.match_indices('\n')
245 .map(|(i, _)| BytePos::new(i as u32 + 1)),
246 )
247 .collect();
248
249 Self {
250 id,
251 name,
252 src,
253 line_starts,
254 }
255 }
256
257 #[must_use]
259 pub fn lookup_line_col(&self, pos: BytePos) -> LineCol {
260 let line_idx = self
261 .line_starts
262 .partition_point(|&start| start.0 <= pos.0)
263 .saturating_sub(1);
264
265 let line_start = self.line_starts[line_idx];
266 let col = pos.0 - line_start.0 + 1;
267
268 LineCol {
269 line: line_idx as u32 + 1,
270 col,
271 }
272 }
273
274 #[must_use]
276 pub fn lookup_line(&self, pos: BytePos) -> usize {
277 self.line_starts
278 .partition_point(|&start| start.0 <= pos.0)
279 .saturating_sub(1)
280 }
281
282 #[must_use]
284 pub fn source_text(&self, span: Span) -> &str {
285 &self.src[span.lo.as_usize()..span.hi.as_usize()]
286 }
287
288 #[must_use]
290 pub fn num_lines(&self) -> usize {
291 self.line_starts.len()
292 }
293
294 #[must_use]
296 pub fn line_content(&self, line_idx: usize) -> Option<&str> {
297 if line_idx >= self.line_starts.len() {
298 return None;
299 }
300
301 let start = self.line_starts[line_idx].as_usize();
302 let end = if line_idx + 1 < self.line_starts.len() {
303 self.line_starts[line_idx + 1].as_usize().saturating_sub(1)
305 } else {
306 self.src.len()
307 };
308
309 Some(&self.src[start..end])
310 }
311
312 #[must_use]
314 pub fn line_start(&self, line_idx: usize) -> Option<BytePos> {
315 self.line_starts.get(line_idx).copied()
316 }
317
318 #[must_use]
323 pub fn span_lines(&self, span: Span) -> SpanLines {
324 let start = self.lookup_line_col(span.lo);
325 let end = if span.hi.0 > span.lo.0 {
327 self.lookup_line_col(BytePos(span.hi.0 - 1))
328 } else {
329 start
330 };
331 SpanLines {
332 start_line: start.line as usize,
333 start_col: start.col as usize,
334 end_line: end.line as usize,
335 end_col: end.col as usize + 1,
337 }
338 }
339}
340
341#[derive(Clone, Copy, Debug, PartialEq, Eq)]
343pub struct SpanLines {
344 pub start_line: usize,
346 pub start_col: usize,
348 pub end_line: usize,
350 pub end_col: usize,
352}
353
354impl SpanLines {
355 #[must_use]
357 pub fn is_multiline(&self) -> bool {
358 self.start_line != self.end_line
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_span_operations() {
368 let span1 = Span::from_raw(10, 20);
369 let span2 = Span::from_raw(15, 30);
370
371 assert_eq!(span1.len(), 10);
372 assert_eq!(span1.merge(span2), Span::from_raw(10, 30));
373 assert!(span1.contains(BytePos::new(15)));
374 assert!(!span1.contains(BytePos::new(25)));
375 }
376
377 #[test]
378 fn test_source_file_line_lookup() {
379 let src = "line 1\nline 2\nline 3";
380 let file = SourceFile::new(FileId::new(0), "test.hs".to_string(), src.to_string());
381
382 assert_eq!(file.lookup_line_col(BytePos::new(0)), LineCol::new(1, 1));
383 assert_eq!(file.lookup_line_col(BytePos::new(7)), LineCol::new(2, 1));
384 assert_eq!(file.lookup_line_col(BytePos::new(10)), LineCol::new(2, 4));
385 }
386
387 #[test]
388 fn test_line_content() {
389 let src = "first line\nsecond line\nthird line";
390 let file = SourceFile::new(FileId::new(0), "test.hs".to_string(), src.to_string());
391
392 assert_eq!(file.line_content(0), Some("first line"));
393 assert_eq!(file.line_content(1), Some("second line"));
394 assert_eq!(file.line_content(2), Some("third line"));
395 assert_eq!(file.line_content(3), None);
396 }
397
398 #[test]
399 fn test_span_lines() {
400 let src = "line 1\nline 2\nline 3";
401 let file = SourceFile::new(FileId::new(0), "test.hs".to_string(), src.to_string());
402
403 let span = Span::from_raw(0, 6);
405 let lines = file.span_lines(span);
406 assert_eq!(lines.start_line, 1);
407 assert_eq!(lines.end_line, 1);
408 assert!(!lines.is_multiline());
409
410 let span = Span::from_raw(0, 14);
412 let lines = file.span_lines(span);
413 assert_eq!(lines.start_line, 1);
414 assert_eq!(lines.end_line, 2);
415 assert!(lines.is_multiline());
416 }
417}