1use std::fmt;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub struct Position {
26 pub line: usize,
28 pub column: usize,
30 pub offset: usize,
32}
33
34impl Position {
35 pub fn new(line: usize, column: usize, offset: usize) -> Self {
43 Position {
44 line,
45 column,
46 offset,
47 }
48 }
49
50 pub fn start() -> Self {
52 Position::new(1, 1, 0)
53 }
54}
55
56impl fmt::Display for Position {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "{}:{}", self.line, self.column)
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub struct Span {
67 pub start: Position,
69 pub end: Position,
71}
72
73impl Span {
74 pub fn new(start: Position, end: Position) -> Self {
76 Span { start, end }
77 }
78
79 pub fn point(pos: Position) -> Self {
81 Span {
82 start: pos,
83 end: pos,
84 }
85 }
86
87 pub fn merge(&self, other: &Span) -> Span {
92 let start = if self.start.offset < other.start.offset {
93 self.start
94 } else {
95 other.start
96 };
97 let end = if self.end.offset > other.end.offset {
98 self.end
99 } else {
100 other.end
101 };
102 Span { start, end }
103 }
104
105 pub fn format_location(&self) -> String {
109 format!("line {}, column {}", self.start.line, self.start.column)
110 }
111
112 pub fn len(&self) -> usize {
114 self.end.offset.saturating_sub(self.start.offset)
115 }
116
117 pub fn is_empty(&self) -> bool {
119 self.start.offset == self.end.offset
120 }
121
122 pub fn is_single_line(&self) -> bool {
124 self.start.line == self.end.line
125 }
126}
127
128impl fmt::Display for Span {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 if self.start.line == self.end.line {
131 write!(
132 f,
133 "{}:{}-{}",
134 self.start.line, self.start.column, self.end.column
135 )
136 } else {
137 write!(f, "{}-{}", self.start, self.end)
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
151 fn test_position_new() {
152 let pos = Position::new(5, 10, 42);
153 assert_eq!(pos.line, 5);
154 assert_eq!(pos.column, 10);
155 assert_eq!(pos.offset, 42);
156 }
157
158 #[test]
159 fn test_position_start() {
160 let pos = Position::start();
161 assert_eq!(pos.line, 1);
162 assert_eq!(pos.column, 1);
163 assert_eq!(pos.offset, 0);
164 }
165
166 #[test]
167 fn test_position_display() {
168 let pos = Position::new(10, 25, 100);
169 assert_eq!(format!("{}", pos), "10:25");
170 }
171
172 #[test]
173 fn test_position_equality() {
174 let pos1 = Position::new(1, 1, 0);
175 let pos2 = Position::new(1, 1, 0);
176 let pos3 = Position::new(1, 2, 1);
177 assert_eq!(pos1, pos2);
178 assert_ne!(pos1, pos3);
179 }
180
181 #[test]
186 fn test_span_new() {
187 let start = Position::new(1, 1, 0);
188 let end = Position::new(1, 5, 4);
189 let span = Span::new(start, end);
190 assert_eq!(span.start, start);
191 assert_eq!(span.end, end);
192 }
193
194 #[test]
195 fn test_span_point() {
196 let pos = Position::new(5, 10, 42);
197 let span = Span::point(pos);
198 assert_eq!(span.start, pos);
199 assert_eq!(span.end, pos);
200 assert!(span.is_empty());
201 }
202
203 #[test]
204 fn test_span_merge_sequential() {
205 let span1 = Span::new(Position::new(1, 1, 0), Position::new(1, 5, 4));
206 let span2 = Span::new(Position::new(1, 5, 4), Position::new(1, 10, 9));
207 let merged = span1.merge(&span2);
208 assert_eq!(merged.start, Position::new(1, 1, 0));
209 assert_eq!(merged.end, Position::new(1, 10, 9));
210 }
211
212 #[test]
213 fn test_span_merge_overlapping() {
214 let span1 = Span::new(Position::new(1, 1, 0), Position::new(1, 7, 6));
215 let span2 = Span::new(Position::new(1, 5, 4), Position::new(1, 10, 9));
216 let merged = span1.merge(&span2);
217 assert_eq!(merged.start, Position::new(1, 1, 0));
218 assert_eq!(merged.end, Position::new(1, 10, 9));
219 }
220
221 #[test]
222 fn test_span_merge_reverse_order() {
223 let span1 = Span::new(Position::new(1, 5, 4), Position::new(1, 10, 9));
224 let span2 = Span::new(Position::new(1, 1, 0), Position::new(1, 5, 4));
225 let merged = span1.merge(&span2);
226 assert_eq!(merged.start, Position::new(1, 1, 0));
227 assert_eq!(merged.end, Position::new(1, 10, 9));
228 }
229
230 #[test]
231 fn test_span_format_location() {
232 let span = Span::new(Position::new(5, 10, 42), Position::new(5, 15, 47));
233 assert_eq!(span.format_location(), "line 5, column 10");
234 }
235
236 #[test]
237 fn test_span_len() {
238 let span = Span::new(Position::new(1, 1, 0), Position::new(1, 5, 4));
239 assert_eq!(span.len(), 4);
240 }
241
242 #[test]
243 fn test_span_is_empty() {
244 let pos = Position::new(1, 1, 0);
245 let empty = Span::point(pos);
246 let non_empty = Span::new(pos, Position::new(1, 5, 4));
247
248 assert!(empty.is_empty());
249 assert!(!non_empty.is_empty());
250 }
251
252 #[test]
253 fn test_span_is_single_line() {
254 let single = Span::new(Position::new(5, 1, 10), Position::new(5, 10, 19));
255 let multi = Span::new(Position::new(5, 1, 10), Position::new(6, 5, 25));
256
257 assert!(single.is_single_line());
258 assert!(!multi.is_single_line());
259 }
260
261 #[test]
262 fn test_span_display_single_line() {
263 let span = Span::new(Position::new(5, 10, 42), Position::new(5, 15, 47));
264 assert_eq!(format!("{}", span), "5:10-15");
265 }
266
267 #[test]
268 fn test_span_display_multi_line() {
269 let span = Span::new(Position::new(5, 10, 42), Position::new(7, 5, 67));
270 assert_eq!(format!("{}", span), "5:10-7:5");
271 }
272
273 #[test]
274 fn test_span_equality() {
275 let span1 = Span::new(Position::new(1, 1, 0), Position::new(1, 5, 4));
276 let span2 = Span::new(Position::new(1, 1, 0), Position::new(1, 5, 4));
277 let span3 = Span::new(Position::new(1, 1, 0), Position::new(1, 6, 5));
278
279 assert_eq!(span1, span2);
280 assert_ne!(span1, span3);
281 }
282
283 #[test]
284 fn test_span_merge_multiline() {
285 let span1 = Span::new(Position::new(1, 1, 0), Position::new(2, 5, 15));
286 let span2 = Span::new(Position::new(3, 1, 20), Position::new(4, 10, 35));
287 let merged = span1.merge(&span2);
288 assert_eq!(merged.start, Position::new(1, 1, 0));
289 assert_eq!(merged.end, Position::new(4, 10, 35));
290 }
291}