use crate::format::node_record::{
NEXT_SIBLING_OFFSET, NODE_DATA_OFFSET, NODE_RECORD_SIZE, PARENT_INDEX_OFFSET, PAYLOAD_MASK,
TYPE_TAG_SHIFT, TypeTag,
};
use crate::format::string_field::{STRING_FIELD_SIZE, StringField};
use super::source_file::LazySourceFile;
#[inline]
#[must_use]
pub fn read_u32(bytes: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap())
}
#[inline]
#[must_use]
pub fn read_u16(bytes: &[u8], offset: usize) -> u16 {
u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap())
}
#[inline]
#[must_use]
pub fn ext_offset(sf: &LazySourceFile<'_>, node_index: u32) -> u32 {
let nd = read_node_data(sf, node_index);
let tag = TypeTag::from_u32((nd >> TYPE_TAG_SHIFT) & 0b11)
.expect("Node Data type tag bits cover 0..=3 by construction");
debug_assert_eq!(
tag,
TypeTag::Extended,
"ext_offset called on a non-Extended node ({tag:?})"
);
sf.extended_data_offset + (nd & PAYLOAD_MASK)
}
#[inline]
#[must_use]
pub fn read_node_data(sf: &LazySourceFile<'_>, node_index: u32) -> u32 {
let off = sf.nodes_offset as usize + node_index as usize * NODE_RECORD_SIZE + NODE_DATA_OFFSET;
read_u32(sf.bytes(), off)
}
#[inline]
#[must_use]
pub fn read_next_sibling(sf: &LazySourceFile<'_>, node_index: u32) -> u32 {
let off =
sf.nodes_offset as usize + node_index as usize * NODE_RECORD_SIZE + NEXT_SIBLING_OFFSET;
read_u32(sf.bytes(), off)
}
#[inline]
#[must_use]
pub fn read_parent_index(sf: &LazySourceFile<'_>, node_index: u32) -> u32 {
let off =
sf.nodes_offset as usize + node_index as usize * NODE_RECORD_SIZE + PARENT_INDEX_OFFSET;
read_u32(sf.bytes(), off)
}
#[inline]
#[must_use]
pub fn first_child(sf: &LazySourceFile<'_>, parent_index: u32) -> Option<u32> {
let candidate = parent_index + 1;
if candidate >= sf.node_count {
return None;
}
if read_parent_index(sf, candidate) == parent_index {
Some(candidate)
} else {
None
}
}
#[inline]
#[must_use]
pub fn read_string_field(bytes: &[u8], offset: usize) -> StringField {
StringField::read_le(&bytes[offset..offset + STRING_FIELD_SIZE])
}
#[inline]
#[must_use]
pub fn read_list_metadata(
sf: &LazySourceFile<'_>,
ext_byte_offset: usize,
slot_offset: usize,
) -> (u32, u32) {
let bytes = sf.bytes();
let head = read_u32(bytes, ext_byte_offset + slot_offset);
let count = read_u16(bytes, ext_byte_offset + slot_offset + 4) as u32;
(head, count)
}
#[inline]
#[must_use]
pub fn string_payload<'a>(sf: &LazySourceFile<'a>, node_index: u32) -> Option<&'a str> {
let nd = read_node_data(sf, node_index);
let tag = (nd >> TYPE_TAG_SHIFT) & 0b11;
let payload = nd & PAYLOAD_MASK;
if tag == TypeTag::StringInline as u32 {
let (offset, length) = crate::format::node_record::unpack_string_inline(payload);
return Some(sf.get_inline_string(offset, length));
}
debug_assert_eq!(
TypeTag::from_u32(tag),
Ok(TypeTag::String),
"string_payload called on a non-String/StringInline node"
);
sf.get_string(payload)
}
#[inline]
#[must_use]
pub fn ext_string_leaf<'a>(sf: &LazySourceFile<'a>, node_index: u32) -> &'a str {
let ext = ext_offset(sf, node_index) as usize;
let field = read_string_field(sf.bytes(), ext);
sf.get_string_by_field(field).unwrap_or("")
}
#[inline]
#[must_use]
pub fn children_bitmask_payload(sf: &LazySourceFile<'_>, node_index: u32) -> u32 {
let nd = read_node_data(sf, node_index);
debug_assert_eq!(
TypeTag::from_u32((nd >> TYPE_TAG_SHIFT) & 0b11),
Ok(TypeTag::Children),
"children_bitmask_payload called on a non-Children node"
);
nd & PAYLOAD_MASK
}
#[inline]
#[must_use]
pub fn child_at_visitor_index(
sf: &LazySourceFile<'_>,
parent_index: u32,
bitmask: u8,
visitor_index: u8,
) -> Option<u32> {
if (bitmask & (1u8 << visitor_index)) == 0 {
return None;
}
let mask_below = (1u8 << visitor_index).wrapping_sub(1);
let skip = (bitmask & mask_below).count_ones();
let mut child = parent_index + 1;
for _ in 0..skip {
let next = read_next_sibling(sf, child);
if next == 0 {
return None; }
child = next;
}
Some(child)
}