use core::fmt;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Position {
pub offset: usize,
pub line: usize,
pub column: usize,
}
impl Position {
pub const fn start() -> Position {
Position {
offset: 0,
line: 1,
column: 1,
}
}
pub fn of(source: &[u8], offset: usize) -> Position {
let mut rv = Position::start();
rv.advance(&source[..offset.min(source.len())]);
rv
}
pub fn advance(&mut self, bytes: &[u8]) {
let mut line_start = None;
find_newlines(bytes, |idx| {
self.line += 1;
line_start = Some(idx + 1);
});
let rest = match line_start {
Some(line_start) => {
self.column = 1;
&bytes[line_start..]
}
None => bytes,
};
self.column += count_chars(rest);
self.offset += bytes.len();
}
}
impl Default for Position {
fn default() -> Position {
Position::start()
}
}
impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
const LO7: u64 = 0x7f7f_7f7f_7f7f_7f7f;
const HI: u64 = 0x8080_8080_8080_8080;
const NEWLINES: u64 = 0x0a0a_0a0a_0a0a_0a0a;
fn zero_bytes(x: u64) -> u64 {
!(((x & LO7).wrapping_add(LO7)) | x | LO7)
}
fn find_newlines<F: FnMut(usize)>(bytes: &[u8], mut f: F) {
let (chunks, rest) = bytes.as_chunks::<8>();
for (idx, &chunk) in chunks.iter().enumerate() {
let mut mask = zero_bytes(u64::from_le_bytes(chunk) ^ NEWLINES);
while mask != 0 {
f(idx * 8 + mask.trailing_zeros() as usize / 8);
mask &= mask - 1;
}
}
let offset = bytes.len() - rest.len();
for (idx, &byte) in rest.iter().enumerate() {
if byte == b'\n' {
f(offset + idx);
}
}
}
fn count_chars(bytes: &[u8]) -> usize {
let (chunks, rest) = bytes.as_chunks::<8>();
let mut continuation = 0;
for &chunk in chunks {
let w = u64::from_le_bytes(chunk);
continuation += (w & !(w << 1) & HI).count_ones() as usize;
}
continuation += rest.iter().filter(|&&b| b & 0xc0 == 0x80).count();
bytes.len() - continuation
}
#[test]
fn test_helpers() {
let mut state = 0x2545f4914f6cdd1du64;
let alphabet = "ab\n\u{e4}\u{1f600}x\n".as_bytes();
let rounds = if cfg!(miri) { 1 } else { 50 };
for len in 0..64 {
for _ in 0..rounds {
let input: Vec<u8> = (0..len)
.map(|_| {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
alphabet[(state % alphabet.len() as u64) as usize]
})
.collect();
let mut newlines = Vec::new();
find_newlines(&input, |idx| newlines.push(idx));
let expected: Vec<usize> = input
.iter()
.enumerate()
.filter(|&(_, &b)| b == b'\n')
.map(|(idx, _)| idx)
.collect();
assert_eq!(newlines, expected);
assert_eq!(
count_chars(&input),
input.iter().filter(|&&b| b & 0xc0 != 0x80).count()
);
let split = input.len() / 3;
let mut pos = Position::start();
pos.advance(&input[..split]);
pos.advance(&input[split..]);
let line_start = input.iter().rposition(|&b| b == b'\n').map_or(0, |x| x + 1);
let expected = Position {
offset: input.len(),
line: newlines.len() + 1,
column: input[line_start..]
.iter()
.filter(|&&b| b & 0xc0 != 0x80)
.count()
+ 1,
};
assert_eq!(pos, expected);
assert_eq!(Position::of(&input, input.len()), expected);
}
}
}
#[test]
fn test_of() {
let source = "ab\ncäd\n\nx".as_bytes();
let pos = |offset| Position::of(source, offset).to_string();
assert_eq!(pos(0), "1:1");
assert_eq!(pos(2), "1:3");
assert_eq!(pos(3), "2:1");
assert_eq!(pos(6), "2:3");
assert_eq!(pos(7), "2:4");
assert_eq!(pos(8), "3:1");
assert_eq!(pos(9), "4:1");
assert_eq!(pos(100), "4:2");
}