Skip to main content

deser_core/
position.rs

1use core::fmt;
2
3/// A position in the input.
4///
5/// Lines and columns are 1-based.  Columns are counted in characters (bytes
6/// that are not UTF-8 continuation bytes).  This is how positions of errors
7/// (see [`Error::line`](crate::Error::line)) are counted as well.
8///
9/// ```
10/// use deser::Position;
11///
12/// let pos = Position::of("[1,\n  x]".as_bytes(), 6);
13/// assert_eq!((pos.offset, pos.line, pos.column), (6, 2, 3));
14/// ```
15#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub struct Position {
17    /// The byte offset from the start of the input.
18    pub offset: usize,
19    /// The line number (1-based).
20    pub line: usize,
21    /// The column number in characters (1-based).
22    pub column: usize,
23}
24
25impl Position {
26    /// Returns the start of the input.
27    pub const fn start() -> Position {
28        Position {
29            offset: 0,
30            line: 1,
31            column: 1,
32        }
33    }
34
35    /// Resolves a byte offset in the source into a position.
36    ///
37    /// Offsets beyond the end of the source are clamped.  This scans the
38    /// source up to the offset, to resolve many offsets in the same source
39    /// use `deser_location::SourceMap`.
40    pub fn of(source: &[u8], offset: usize) -> Position {
41        let mut rv = Position::start();
42        rv.advance(&source[..offset.min(source.len())]);
43        rv
44    }
45
46    /// Advances the position over the given bytes.
47    ///
48    /// ```
49    /// use deser::Position;
50    ///
51    /// let mut pos = Position::start();
52    /// pos.advance("ab\nc".as_bytes());
53    /// assert_eq!(pos.to_string(), "2:2");
54    /// pos.advance("äd".as_bytes());
55    /// assert_eq!(pos.to_string(), "2:4");
56    /// ```
57    pub fn advance(&mut self, bytes: &[u8]) {
58        let mut line_start = None;
59        find_newlines(bytes, |idx| {
60            self.line += 1;
61            line_start = Some(idx + 1);
62        });
63        let rest = match line_start {
64            Some(line_start) => {
65                self.column = 1;
66                &bytes[line_start..]
67            }
68            None => bytes,
69        };
70        self.column += count_chars(rest);
71        self.offset += bytes.len();
72    }
73}
74
75impl Default for Position {
76    fn default() -> Position {
77        Position::start()
78    }
79}
80
81impl fmt::Debug for Position {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "{}:{}", self.line, self.column)
84    }
85}
86
87impl fmt::Display for Position {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        write!(f, "{}:{}", self.line, self.column)
90    }
91}
92
93// The helpers below process the input a word at a time as positions are
94// resolved for errors and (with deser-location) for every event.
95
96const LO7: u64 = 0x7f7f_7f7f_7f7f_7f7f;
97const HI: u64 = 0x8080_8080_8080_8080;
98const NEWLINES: u64 = 0x0a0a_0a0a_0a0a_0a0a;
99
100/// Sets the high bit of every byte that is zero (exact, no false positives).
101fn zero_bytes(x: u64) -> u64 {
102    !(((x & LO7).wrapping_add(LO7)) | x | LO7)
103}
104
105/// Invokes the callback with the index of every newline.
106fn find_newlines<F: FnMut(usize)>(bytes: &[u8], mut f: F) {
107    let (chunks, rest) = bytes.as_chunks::<8>();
108    for (idx, &chunk) in chunks.iter().enumerate() {
109        let mut mask = zero_bytes(u64::from_le_bytes(chunk) ^ NEWLINES);
110        while mask != 0 {
111            f(idx * 8 + mask.trailing_zeros() as usize / 8);
112            mask &= mask - 1;
113        }
114    }
115    let offset = bytes.len() - rest.len();
116    for (idx, &byte) in rest.iter().enumerate() {
117        if byte == b'\n' {
118            f(offset + idx);
119        }
120    }
121}
122
123/// Counts the characters (bytes that are not utf-8 continuation bytes).
124fn count_chars(bytes: &[u8]) -> usize {
125    let (chunks, rest) = bytes.as_chunks::<8>();
126    let mut continuation = 0;
127    for &chunk in chunks {
128        let w = u64::from_le_bytes(chunk);
129        // high bit set and the bit below it cleared
130        continuation += (w & !(w << 1) & HI).count_ones() as usize;
131    }
132    continuation += rest.iter().filter(|&&b| b & 0xc0 == 0x80).count();
133    bytes.len() - continuation
134}
135
136#[test]
137fn test_helpers() {
138    let mut state = 0x2545f4914f6cdd1du64;
139    let alphabet = "ab\n\u{e4}\u{1f600}x\n".as_bytes();
140    let rounds = if cfg!(miri) { 1 } else { 50 };
141    for len in 0..64 {
142        for _ in 0..rounds {
143            let input: Vec<u8> = (0..len)
144                .map(|_| {
145                    state ^= state << 13;
146                    state ^= state >> 7;
147                    state ^= state << 17;
148                    alphabet[(state % alphabet.len() as u64) as usize]
149                })
150                .collect();
151            let mut newlines = Vec::new();
152            find_newlines(&input, |idx| newlines.push(idx));
153            let expected: Vec<usize> = input
154                .iter()
155                .enumerate()
156                .filter(|&(_, &b)| b == b'\n')
157                .map(|(idx, _)| idx)
158                .collect();
159            assert_eq!(newlines, expected);
160            assert_eq!(
161                count_chars(&input),
162                input.iter().filter(|&&b| b & 0xc0 != 0x80).count()
163            );
164
165            // advancing in pieces is the same as in one go
166            let split = input.len() / 3;
167            let mut pos = Position::start();
168            pos.advance(&input[..split]);
169            pos.advance(&input[split..]);
170            let line_start = input.iter().rposition(|&b| b == b'\n').map_or(0, |x| x + 1);
171            let expected = Position {
172                offset: input.len(),
173                line: newlines.len() + 1,
174                column: input[line_start..]
175                    .iter()
176                    .filter(|&&b| b & 0xc0 != 0x80)
177                    .count()
178                    + 1,
179            };
180            assert_eq!(pos, expected);
181            assert_eq!(Position::of(&input, input.len()), expected);
182        }
183    }
184}
185
186#[test]
187fn test_of() {
188    let source = "ab\ncäd\n\nx".as_bytes();
189    let pos = |offset| Position::of(source, offset).to_string();
190    assert_eq!(pos(0), "1:1");
191    assert_eq!(pos(2), "1:3");
192    assert_eq!(pos(3), "2:1");
193    // ä is two bytes
194    assert_eq!(pos(6), "2:3");
195    assert_eq!(pos(7), "2:4");
196    assert_eq!(pos(8), "3:1");
197    assert_eq!(pos(9), "4:1");
198    // clamped
199    assert_eq!(pos(100), "4:2");
200}