1use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct Position {
8 pub line: usize,
10 pub column: usize,
12}
13
14impl Position {
15 pub const fn new(line: usize, column: usize) -> Self {
17 Self { line, column }
18 }
19}
20
21impl fmt::Display for Position {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "{}:{}", self.line, self.column)
24 }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub struct Span {
33 pub start: usize,
35 pub end: usize,
37}
38
39impl Span {
40 pub const fn new(start: usize, end: usize) -> Self {
42 Self { start, end }
43 }
44
45 pub const fn single(pos: usize) -> Self {
47 Self {
48 start: pos,
49 end: pos + 1,
50 }
51 }
52
53 pub const fn merge(self, other: Span) -> Span {
55 let start = if self.start < other.start {
56 self.start
57 } else {
58 other.start
59 };
60 let end = if self.end > other.end {
61 self.end
62 } else {
63 other.end
64 };
65 Span { start, end }
66 }
67
68 pub const fn len(&self) -> usize {
70 self.end - self.start
71 }
72
73 pub const fn is_empty(&self) -> bool {
75 self.start == self.end
76 }
77
78 pub fn slice<'a>(&self, source: &'a str) -> &'a str {
80 &source[self.start..self.end]
81 }
82
83 pub fn to_positions(&self, source: &str) -> (Position, Position) {
88 let start_pos = byte_offset_to_position(source, self.start);
89 let end_pos = byte_offset_to_position(source, self.end);
90 (start_pos, end_pos)
91 }
92}
93
94impl fmt::Display for Span {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(f, "{}..{}", self.start, self.end)
97 }
98}
99
100fn byte_offset_to_position(source: &str, offset: usize) -> Position {
102 let mut line = 1;
103 let mut column = 1;
104
105 for (i, ch) in source.char_indices() {
106 if i >= offset {
107 break;
108 }
109 if ch == '\n' {
110 line += 1;
111 column = 1;
112 } else {
113 column += 1;
114 }
115 }
116
117 Position { line, column }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_position_display() {
126 let pos = Position::new(10, 25);
127 assert_eq!(pos.to_string(), "10:25");
128 }
129
130 #[test]
131 fn test_span_merge() {
132 let span1 = Span::new(5, 10);
133 let span2 = Span::new(8, 15);
134 let merged = span1.merge(span2);
135 assert_eq!(merged, Span::new(5, 15));
136 }
137
138 #[test]
139 fn test_span_len() {
140 let span = Span::new(10, 20);
141 assert_eq!(span.len(), 10);
142 }
143
144 #[test]
145 fn test_span_slice() {
146 let source = "hello world";
147 let span = Span::new(0, 5);
148 assert_eq!(span.slice(source), "hello");
149 }
150
151 #[test]
152 fn test_byte_offset_to_position() {
153 let source = "hello\nworld\nfoo";
154 assert_eq!(byte_offset_to_position(source, 0), Position::new(1, 1));
155 assert_eq!(byte_offset_to_position(source, 5), Position::new(1, 6));
156 assert_eq!(byte_offset_to_position(source, 6), Position::new(2, 1));
157 assert_eq!(byte_offset_to_position(source, 12), Position::new(3, 1));
158 }
159
160 #[test]
161 fn test_span_to_positions() {
162 let source = "hello\nworld";
163 let span = Span::new(0, 5);
164 let (start, end) = span.to_positions(source);
165 assert_eq!(start, Position::new(1, 1));
166 assert_eq!(end, Position::new(1, 6));
167 }
168}