use std::iter::zip;
use std::mem::MaybeUninit;
use seq_macro::seq;
pub(crate) const BITS_PER_U8: usize = 8;
pub(crate) const BITS_PER_U32: usize = 32;
pub(crate) const BITS_PER_NUC: usize = 2;
pub(crate) const NUCS_PER_U8: usize = BITS_PER_U8 / BITS_PER_NUC;
const DECODE_U2: [u8; 4] = *b"TCAG";
#[allow(clippy::identity_op)] #[allow(clippy::erasing_op)]
#[allow(clippy::eq_op)]
const DECODE_U8: [u32; 256] = seq!(i in 0..256 {[#(
u32::from_le_bytes([DECODE_U2[(i / 64) % 4],
DECODE_U2[(i / 16) % 4],
DECODE_U2[(i / 4) % 4],
DECODE_U2[(i / 1) % 4]]),
)*]});
pub(crate) fn decode(start: usize, dna: &[u8], dst: &mut [MaybeUninit<u8>]) {
let (dst_quads, dst_tail) = dst.as_chunks_mut::<NUCS_PER_U8>();
let start_byte = start / NUCS_PER_U8;
let start_align = start % NUCS_PER_U8;
let tail_start_byte = start_byte + dst_quads.len();
match start_align {
0 => decode_quads::<0>(start_byte, dna, dst_quads),
1 => decode_quads::<1>(start_byte, dna, dst_quads),
2 => decode_quads::<2>(start_byte, dna, dst_quads),
3 => decode_quads::<3>(start_byte, dna, dst_quads),
_ => unreachable!(),
};
decode_subquad(tail_start_byte, start_align, dna, dst_tail);
}
fn decode_subquad(start_byte: usize, start_align: usize, dna: &[u8], dst: &mut [MaybeUninit<u8>]) {
if dst.is_empty() {
return;
}
assert!(dst.len() < NUCS_PER_U8);
let mut nucs = DECODE_U8[dna[start_byte] as usize];
if start_align > 0 {
nucs >>= BITS_PER_U8 * start_align;
if dst.len() > NUCS_PER_U8 - start_align {
let extra_nucs = DECODE_U8[dna[start_byte + 1] as usize];
nucs |= extra_nucs << (BITS_PER_U32 - BITS_PER_U8 * start_align);
}
}
for d in dst {
d.write((nucs & 0xff) as u8);
nucs >>= BITS_PER_U8;
}
}
fn decode_quads<const START_ALIGN: usize>(start_byte: usize, dna: &[u8], dst: &mut [[MaybeUninit<u8>; NUCS_PER_U8]]) {
if dst.is_empty() {
return;
}
let src_len = dst.len() + START_ALIGN.min(1);
let src = &dna[start_byte..start_byte + src_len];
if START_ALIGN == 0 {
for (s, d) in zip(src, dst) {
d.write_copy_of_slice(&DECODE_U8[*s as usize].to_le_bytes());
}
} else {
let mut prev = DECODE_U8[src[0] as usize];
for (s, d) in zip(&src[1..], dst) {
let next = DECODE_U8[*s as usize];
d.write_copy_of_slice(&combine_quads::<START_ALIGN>(prev, next).to_le_bytes());
prev = next;
}
}
}
#[inline]
fn combine_quads<const START_ALIGN: usize>(prev: u32, next: u32) -> u32 {
(prev >> (BITS_PER_U8 * START_ALIGN)) | (next << (BITS_PER_U32 - BITS_PER_U8 * START_ALIGN))
}