1#[derive(Debug, Clone, PartialEq, Eq)]
3pub struct SourcePos {
4 pub file: String,
6
7 pub line: u32,
9
10 pub column: u32,
12
13 pub length: u32,
15}
16
17impl SourcePos {
18 pub fn new(file: String, line: u32, column: u32, length: u32) -> Self {
20 assert!(line > 0, "Line numbers are 1-indexed");
21 assert!(column > 0, "Column numbers are 1-indexed");
22 Self { file, line, column, length }
23 }
24
25 pub fn repl(line: u32, column: u32, length: u32) -> Self {
27 Self::new("<repl>".to_string(), line, column, length)
28 }
29
30 pub fn end_column(&self) -> u32 {
32 self.column + self.length
33 }
34
35 pub fn contains(&self, other: &SourcePos) -> bool {
37 self.file == other.file
38 && self.line == other.line
39 && other.column >= self.column
40 && other.column < self.end_column()
41 }
42}
43
44impl std::fmt::Display for SourcePos {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 write!(f, "{}:{}:{}", self.file, self.line, self.column)
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn test_source_pos_basic() {
56 let pos = SourcePos::new("test.oxur".to_string(), 1, 5, 10);
57 assert_eq!(pos.line, 1);
58 assert_eq!(pos.column, 5);
59 assert_eq!(pos.length, 10);
60 assert_eq!(pos.end_column(), 15);
61 }
62
63 #[test]
64 fn test_source_pos_display() {
65 let pos = SourcePos::new("test.oxur".to_string(), 10, 20, 5);
66 assert_eq!(format!("{}", pos), "test.oxur:10:20");
67 }
68
69 #[test]
70 fn test_source_pos_repl() {
71 let pos = SourcePos::repl(1, 1, 20);
72 assert_eq!(pos.file, "<repl>");
73 assert_eq!(pos.line, 1);
74 assert_eq!(pos.column, 1);
75 assert_eq!(pos.length, 20);
76 }
77
78 #[test]
79 fn test_source_pos_contains() {
80 let span = SourcePos::new("test.oxur".to_string(), 1, 5, 10);
81 let inner = SourcePos::new("test.oxur".to_string(), 1, 7, 3);
82 let outer = SourcePos::new("test.oxur".to_string(), 1, 3, 2);
83
84 assert!(span.contains(&inner));
85 assert!(!span.contains(&outer));
86 }
87
88 #[test]
89 fn test_source_pos_contains_different_files() {
90 let span1 = SourcePos::new("file1.oxur".to_string(), 1, 5, 10);
91 let span2 = SourcePos::new("file2.oxur".to_string(), 1, 7, 3);
92
93 assert!(!span1.contains(&span2));
94 }
95
96 #[test]
97 fn test_source_pos_contains_different_lines() {
98 let span1 = SourcePos::new("test.oxur".to_string(), 1, 5, 10);
99 let span2 = SourcePos::new("test.oxur".to_string(), 2, 7, 3);
100
101 assert!(!span1.contains(&span2));
102 }
103
104 #[test]
105 #[should_panic(expected = "Line numbers are 1-indexed")]
106 fn test_source_pos_zero_line() {
107 SourcePos::new("test.oxur".to_string(), 0, 1, 1);
108 }
109
110 #[test]
111 #[should_panic(expected = "Column numbers are 1-indexed")]
112 fn test_source_pos_zero_column() {
113 SourcePos::new("test.oxur".to_string(), 1, 0, 1);
114 }
115}