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(src.match_indices('\n').map(|(i, _)| BytePos::new(i as u32 + 1)))
244 .collect();
245
246 Self {
247 id,
248 name,
249 src,
250 line_starts,
251 }
252 }
253
254 #[must_use]
256 pub fn lookup_line_col(&self, pos: BytePos) -> LineCol {
257 let line_idx = self
258 .line_starts
259 .partition_point(|&start| start.0 <= pos.0)
260 .saturating_sub(1);
261
262 let line_start = self.line_starts[line_idx];
263 let col = pos.0 - line_start.0 + 1;
264
265 LineCol {
266 line: line_idx as u32 + 1,
267 col,
268 }
269 }
270
271 #[must_use]
273 pub fn lookup_line(&self, pos: BytePos) -> usize {
274 self.line_starts
275 .partition_point(|&start| start.0 <= pos.0)
276 .saturating_sub(1)
277 }
278
279 #[must_use]
281 pub fn source_text(&self, span: Span) -> &str {
282 &self.src[span.lo.as_usize()..span.hi.as_usize()]
283 }
284
285 #[must_use]
287 pub fn num_lines(&self) -> usize {
288 self.line_starts.len()
289 }
290
291 #[must_use]
293 pub fn line_content(&self, line_idx: usize) -> Option<&str> {
294 if line_idx >= self.line_starts.len() {
295 return None;
296 }
297
298 let start = self.line_starts[line_idx].as_usize();
299 let end = if line_idx + 1 < self.line_starts.len() {
300 self.line_starts[line_idx + 1].as_usize().saturating_sub(1)
302 } else {
303 self.src.len()
304 };
305
306 Some(&self.src[start..end])
307 }
308
309 #[must_use]
311 pub fn line_start(&self, line_idx: usize) -> Option<BytePos> {
312 self.line_starts.get(line_idx).copied()
313 }
314
315 #[must_use]
320 pub fn span_lines(&self, span: Span) -> SpanLines {
321 let start = self.lookup_line_col(span.lo);
322 let end = if span.hi.0 > span.lo.0 {
324 self.lookup_line_col(BytePos(span.hi.0 - 1))
325 } else {
326 start
327 };
328 SpanLines {
329 start_line: start.line as usize,
330 start_col: start.col as usize,
331 end_line: end.line as usize,
332 end_col: end.col as usize + 1,
334 }
335 }
336}
337
338#[derive(Clone, Copy, Debug, PartialEq, Eq)]
340pub struct SpanLines {
341 pub start_line: usize,
343 pub start_col: usize,
345 pub end_line: usize,
347 pub end_col: usize,
349}
350
351impl SpanLines {
352 #[must_use]
354 pub fn is_multiline(&self) -> bool {
355 self.start_line != self.end_line
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_span_operations() {
365 let span1 = Span::from_raw(10, 20);
366 let span2 = Span::from_raw(15, 30);
367
368 assert_eq!(span1.len(), 10);
369 assert_eq!(span1.merge(span2), Span::from_raw(10, 30));
370 assert!(span1.contains(BytePos::new(15)));
371 assert!(!span1.contains(BytePos::new(25)));
372 }
373
374 #[test]
375 fn test_source_file_line_lookup() {
376 let src = "line 1\nline 2\nline 3";
377 let file = SourceFile::new(FileId::new(0), "test.hs".to_string(), src.to_string());
378
379 assert_eq!(file.lookup_line_col(BytePos::new(0)), LineCol::new(1, 1));
380 assert_eq!(file.lookup_line_col(BytePos::new(7)), LineCol::new(2, 1));
381 assert_eq!(file.lookup_line_col(BytePos::new(10)), LineCol::new(2, 4));
382 }
383
384 #[test]
385 fn test_line_content() {
386 let src = "first line\nsecond line\nthird line";
387 let file = SourceFile::new(FileId::new(0), "test.hs".to_string(), src.to_string());
388
389 assert_eq!(file.line_content(0), Some("first line"));
390 assert_eq!(file.line_content(1), Some("second line"));
391 assert_eq!(file.line_content(2), Some("third line"));
392 assert_eq!(file.line_content(3), None);
393 }
394
395 #[test]
396 fn test_span_lines() {
397 let src = "line 1\nline 2\nline 3";
398 let file = SourceFile::new(FileId::new(0), "test.hs".to_string(), src.to_string());
399
400 let span = Span::from_raw(0, 6);
402 let lines = file.span_lines(span);
403 assert_eq!(lines.start_line, 1);
404 assert_eq!(lines.end_line, 1);
405 assert!(!lines.is_multiline());
406
407 let span = Span::from_raw(0, 14);
409 let lines = file.span_lines(span);
410 assert_eq!(lines.start_line, 1);
411 assert_eq!(lines.end_line, 2);
412 assert!(lines.is_multiline());
413 }
414}