perl_position_tracking/
position.rs1use serde::{Deserialize, Serialize};
15use std::fmt;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
22pub struct Position {
23 pub byte: usize,
25 pub line: u32,
27 pub column: u32,
29}
30
31impl Position {
32 pub fn new(byte: usize, line: u32, column: u32) -> Self {
34 Position { byte, line, column }
35 }
36
37 pub fn start() -> Self {
39 Position { byte: 0, line: 1, column: 1 }
40 }
41
42 pub fn advance(&mut self, text: &str) {
44 for ch in text.chars() {
45 if ch == '\n' {
46 self.line += 1;
47 self.column = 1;
48 } else {
49 self.column += 1;
50 }
51 self.byte += ch.len_utf8();
52 }
53 }
54
55 pub fn advance_char(&mut self, ch: char) {
57 if ch == '\n' {
58 self.line += 1;
59 self.column = 1;
60 } else {
61 self.column += 1;
62 }
63 self.byte += ch.len_utf8();
64 }
65}
66
67impl fmt::Display for Position {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "{}:{}", self.line, self.column)
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77pub struct Range {
78 pub start: Position,
80 pub end: Position,
82}
83
84impl Range {
85 pub fn new(start: Position, end: Position) -> Self {
87 Range { start, end }
88 }
89
90 pub fn empty(pos: Position) -> Self {
92 Range { start: pos, end: pos }
93 }
94
95 pub fn contains_byte(&self, byte: usize) -> bool {
97 self.start.byte <= byte && byte < self.end.byte
98 }
99
100 pub fn contains(&self, pos: Position) -> bool {
102 self.start.byte <= pos.byte && pos.byte < self.end.byte
103 }
104
105 pub fn overlaps(&self, other: &Range) -> bool {
107 self.start.byte < other.end.byte && other.start.byte < self.end.byte
108 }
109
110 pub fn len(&self) -> usize {
112 self.end.byte.saturating_sub(self.start.byte)
113 }
114
115 pub fn is_empty(&self) -> bool {
117 self.start.byte >= self.end.byte
118 }
119
120 pub fn extend(&mut self, other: &Range) {
122 if other.start.byte < self.start.byte {
123 self.start = other.start;
124 }
125 if other.end.byte > self.end.byte {
126 self.end = other.end;
127 }
128 }
129
130 pub fn span_to(&self, other: &Range) -> Range {
132 Range {
133 start: if self.start.byte < other.start.byte { self.start } else { other.start },
134 end: if self.end.byte > other.end.byte { self.end } else { other.end },
135 }
136 }
137}
138
139impl fmt::Display for Range {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 write!(f, "{}-{}", self.start, self.end)
142 }
143}
144
145impl From<crate::SourceLocation> for Range {
147 fn from(loc: crate::SourceLocation) -> Self {
148 Range {
150 start: Position { byte: loc.start, line: 0, column: 0 },
151 end: Position { byte: loc.end, line: 0, column: 0 },
152 }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_position_advance() {
162 let mut pos = Position::start();
163 assert_eq!(pos, Position { byte: 0, line: 1, column: 1 });
164
165 pos.advance("hello");
166 assert_eq!(pos, Position { byte: 5, line: 1, column: 6 });
167
168 pos.advance("\n");
169 assert_eq!(pos, Position { byte: 6, line: 2, column: 1 });
170
171 pos.advance("世界"); assert_eq!(pos, Position { byte: 12, line: 2, column: 3 });
173 }
174
175 #[test]
176 fn test_range_operations() {
177 let start = Position::new(10, 2, 5);
178 let end = Position::new(20, 3, 10);
179 let range = Range::new(start, end);
180
181 assert!(range.contains_byte(15));
182 assert!(!range.contains_byte(25));
183 assert_eq!(range.len(), 10);
184
185 let other = Range::new(Position::new(15, 2, 10), Position::new(25, 4, 5));
186
187 assert!(range.overlaps(&other));
188
189 let span = range.span_to(&other);
190 assert_eq!(span.start.byte, 10);
191 assert_eq!(span.end.byte, 25);
192 }
193}