1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct Pos {
8 pub byte_offset: usize,
9 pub char_offset: usize,
10 pub line: usize,
11 pub column: usize,
12}
13
14impl Pos {
15 pub const ORIGIN: Self = Self {
17 byte_offset: 0,
18 char_offset: 0,
19 line: 1,
20 column: 0,
21 };
22
23 #[must_use]
30 pub const fn advance(self, ch: char) -> Self {
31 let byte_offset = self.byte_offset + ch.len_utf8();
32 let char_offset = self.char_offset + 1;
33 if ch == '\n' {
34 Self {
35 byte_offset,
36 char_offset,
37 line: self.line + 1,
38 column: 0,
39 }
40 } else {
41 Self {
42 byte_offset,
43 char_offset,
44 line: self.line,
45 column: self.column + 1,
46 }
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct Span {
54 pub start: Pos,
55 pub end: Pos,
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn pos_origin_is_start_of_document() {
64 let pos = Pos::ORIGIN;
65 assert_eq!(pos.byte_offset, 0);
66 assert_eq!(pos.char_offset, 0);
67 assert_eq!(pos.line, 1);
68 assert_eq!(pos.column, 0);
69 }
70
71 #[test]
72 fn pos_fields_are_accessible() {
73 let pos = Pos {
74 byte_offset: 10,
75 char_offset: 8,
76 line: 3,
77 column: 4,
78 };
79 assert_eq!(pos.byte_offset, 10);
80 assert_eq!(pos.char_offset, 8);
81 assert_eq!(pos.line, 3);
82 assert_eq!(pos.column, 4);
83 }
84
85 #[test]
86 fn pos_is_copy() {
87 let pos = Pos::ORIGIN;
88 let pos2 = pos;
89 let _ = pos.byte_offset;
90 let _ = pos2.byte_offset;
91 }
92
93 #[test]
94 fn span_is_copy() {
95 let span = Span {
96 start: Pos::ORIGIN,
97 end: Pos::ORIGIN,
98 };
99 let span2 = span;
100 let _ = span.start;
101 let _ = span2.start;
102 }
103
104 #[test]
105 fn advance_ascii_increments_byte_and_char_and_column() {
106 let pos = Pos::ORIGIN.advance('a');
107 assert_eq!(pos.byte_offset, 1);
108 assert_eq!(pos.char_offset, 1);
109 assert_eq!(pos.line, 1);
110 assert_eq!(pos.column, 1);
111 }
112
113 #[test]
114 fn advance_newline_increments_line_and_resets_column() {
115 let pos = Pos::ORIGIN.advance('a').advance('\n');
116 assert_eq!(pos.byte_offset, 2);
117 assert_eq!(pos.char_offset, 2);
118 assert_eq!(pos.line, 2);
119 assert_eq!(pos.column, 0);
120 }
121
122 #[test]
123 fn advance_multibyte_char_increments_byte_offset_by_utf8_len() {
124 let pos = Pos::ORIGIN.advance('中');
126 assert_eq!(pos.byte_offset, 3);
127 assert_eq!(pos.char_offset, 1);
128 assert_eq!(pos.line, 1);
129 assert_eq!(pos.column, 1);
130 }
131
132 #[test]
133 fn advance_multiple_lines() {
134 let pos = Pos::ORIGIN
135 .advance('a')
136 .advance('\n')
137 .advance('b')
138 .advance('\n')
139 .advance('c');
140 assert_eq!(pos.line, 3);
141 assert_eq!(pos.column, 1);
142 }
143}