use bytes::Bytes;
use crate::error::{Error, Result};
pub struct LeCursor<'a> {
buf: &'a [u8],
pos: usize,
base: u64,
path: &'a str,
}
impl<'a> LeCursor<'a> {
pub fn new(buf: &'a [u8], base: u64, path: &'a str) -> Self {
Self {
buf,
pos: 0,
base,
path,
}
}
pub fn position(&self) -> usize {
self.pos
}
pub fn file_offset(&self) -> u64 {
self.base + self.pos as u64
}
pub fn remaining(&self) -> usize {
self.buf.len() - self.pos
}
pub fn is_empty(&self) -> bool {
self.remaining() == 0
}
pub fn seek(&mut self, pos: usize) -> Result<()> {
if pos > self.buf.len() {
return Err(self.short(pos - self.buf.len(), "seek"));
}
self.pos = pos;
Ok(())
}
pub fn skip(&mut self, n: usize) -> Result<()> {
let pos = self
.pos
.checked_add(n)
.ok_or_else(|| self.short(n, "skip"))?;
self.seek(pos)
}
pub fn take(&mut self, n: usize) -> Result<&'a [u8]> {
let end = self
.pos
.checked_add(n)
.ok_or_else(|| self.short(n, "take"))?;
if end > self.buf.len() {
return Err(self.short(n, "take"));
}
let out = &self.buf[self.pos..end];
self.pos = end;
Ok(out)
}
pub fn take_cstr(&mut self) -> Result<&'a str> {
let rest = &self.buf[self.pos..];
let nul = memchr::memchr(0, rest).ok_or_else(|| self.corrupt("unterminated string"))?;
let out = std::str::from_utf8(&rest[..nul])
.map_err(|_| self.corrupt("string is not valid UTF-8"))?;
self.pos += nul + 1;
Ok(out)
}
pub fn take_padded_str(&mut self, width: usize) -> Result<&'a str> {
let field = self.take(width)?;
let end = memchr::memchr(0, field).unwrap_or(width);
std::str::from_utf8(&field[..end]).map_err(|_| self.corrupt("string is not valid UTF-8"))
}
fn short(&self, wanted: usize, what: &str) -> Error {
Error::corrupt(
self.path,
self.file_offset(),
format!(
"{what} of {wanted} bytes with {} left in a {}-byte buffer",
self.remaining(),
self.buf.len()
),
)
}
fn corrupt(&self, what: &str) -> Error {
Error::corrupt(self.path, self.file_offset(), what)
}
}
macro_rules! le_readers {
($($name:ident, $peek:ident => $t:ty),* $(,)?) => {
impl LeCursor<'_> {
$(
#[inline]
pub fn $name(&mut self) -> Result<$t> {
const N: usize = std::mem::size_of::<$t>();
let bytes = self.take(N)?;
let mut arr = [0u8; N];
arr.copy_from_slice(bytes);
Ok(<$t>::from_le_bytes(arr))
}
#[inline]
pub fn $peek(&self) -> Result<$t> {
const N: usize = std::mem::size_of::<$t>();
if self.remaining() < N {
return Err(self.short(N, stringify!($peek)));
}
let mut arr = [0u8; N];
arr.copy_from_slice(&self.buf[self.pos..self.pos + N]);
Ok(<$t>::from_le_bytes(arr))
}
)*
}
};
}
le_readers! {
read_u8, peek_u8 => u8,
read_i8, peek_i8 => i8,
read_u16, peek_u16 => u16,
read_i16, peek_i16 => i16,
read_u32, peek_u32 => u32,
read_i32, peek_i32 => i32,
read_u64, peek_u64 => u64,
read_i64, peek_i64 => i64,
read_f32, peek_f32 => f32,
read_f64, peek_f64 => f64,
}
#[derive(Default)]
pub struct LeBuf {
buf: Vec<u8>,
}
impl LeBuf {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(n: usize) -> Self {
Self {
buf: Vec::with_capacity(n),
}
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn clear(&mut self) {
self.buf.clear();
}
pub fn as_slice(&self) -> &[u8] {
&self.buf
}
pub fn into_bytes(self) -> Bytes {
Bytes::from(self.buf)
}
pub fn put_bytes(&mut self, data: &[u8]) {
self.buf.extend_from_slice(data);
}
pub fn put_zeros(&mut self, n: usize) {
self.buf.resize(self.buf.len() + n, 0);
}
pub fn put_padded_str(&mut self, s: &str, width: usize) -> Result<()> {
if s.len() > width {
return Err(Error::invalid(format!(
"chromosome name {s:?} is longer than the {width}-byte key field"
)));
}
self.buf.extend_from_slice(s.as_bytes());
self.put_zeros(width - s.len());
Ok(())
}
}
macro_rules! le_writers {
($($name:ident, $set:ident => $t:ty),* $(,)?) => {
impl LeBuf {
$(
#[inline]
pub fn $name(&mut self, value: $t) {
self.buf.extend_from_slice(&value.to_le_bytes());
}
#[inline]
pub fn $set(&mut self, offset: usize, value: $t) {
let bytes = value.to_le_bytes();
self.buf[offset..offset + bytes.len()].copy_from_slice(&bytes);
}
)*
}
};
}
le_writers! {
put_u8, set_u8 => u8,
put_u16, set_u16 => u16,
put_u32, set_u32 => u32,
put_i32, set_i32 => i32,
put_u64, set_u64 => u64,
put_i64, set_i64 => i64,
put_f32, set_f32 => f32,
put_f64, set_f64 => f64,
}