1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
9pub struct Span {
10 pub start: u32,
11 pub end: u32,
12}
13
14impl Span {
15 #[must_use]
16 pub const fn new(start: u32, end: u32) -> Self {
17 Self { start, end }
18 }
19
20 #[must_use]
21 pub const fn point(offset: u32) -> Self {
22 Self {
23 start: offset,
24 end: offset,
25 }
26 }
27
28 #[must_use]
29 pub fn len(self) -> u32 {
30 self.end.saturating_sub(self.start)
31 }
32
33 #[must_use]
34 pub fn is_empty(self) -> bool {
35 self.len() == 0
36 }
37
38 #[must_use]
41 pub fn union(self, other: Span) -> Span {
42 Span {
43 start: self.start.min(other.start),
44 end: self.end.max(other.end),
45 }
46 }
47
48 #[must_use]
49 pub fn slice<'a>(self, src: &'a str) -> &'a str {
50 let start = self.start as usize;
51 let end = self.end as usize;
52 if start >= src.len() {
53 ""
54 } else {
55 let end = end.min(src.len());
56 &src[start..end]
57 }
58 }
59
60 #[must_use]
61 pub fn contains(self, offset: u32) -> bool {
62 offset >= self.start && offset < self.end
63 }
64}
65
66impl fmt::Display for Span {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 write!(f, "{}..{}", self.start, self.end)
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74pub struct Position {
75 pub line: u32,
76 pub column: u32,
77}
78
79impl fmt::Display for Position {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 write!(f, "{}:{}", self.line, self.column)
82 }
83}
84
85#[must_use]
88pub fn line_column(src: &str, offset: u32) -> Position {
89 let mut line: u32 = 1;
90 let mut col: u32 = 1;
91 let offset = offset as usize;
92 for (i, ch) in src.char_indices() {
93 if i >= offset {
94 break;
95 }
96 if ch == '\n' {
97 line += 1;
98 col = 1;
99 } else {
100 col += 1;
101 }
102 }
103 Position { line, column: col }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn point_span_is_empty() {
112 let s = Span::point(5);
113 assert!(s.is_empty());
114 assert_eq!(s.len(), 0);
115 }
116
117 #[test]
118 fn union_widens() {
119 let a = Span::new(2, 5);
120 let b = Span::new(4, 9);
121 let u = a.union(b);
122 assert_eq!(u.start, 2);
123 assert_eq!(u.end, 9);
124 }
125
126 #[test]
127 fn slice_extracts_substring() {
128 let src = "hello world";
129 assert_eq!(Span::new(6, 11).slice(src), "world");
130 }
131
132 #[test]
133 fn line_column_handles_newlines() {
134 let src = "abc\ndef\nghi";
135 assert_eq!(line_column(src, 0), Position { line: 1, column: 1 });
136 assert_eq!(line_column(src, 4), Position { line: 2, column: 1 });
137 assert_eq!(line_column(src, 9), Position { line: 3, column: 2 });
138 }
139}