pub const HEADER_SIZE: usize = 40;
pub const VERSION_OFFSET: usize = 0;
pub const FLAGS_OFFSET: usize = 1;
pub const RESERVED_OFFSET: usize = 2;
pub const ROOT_ARRAY_OFFSET_FIELD: usize = 4;
pub const STRING_OFFSETS_OFFSET_FIELD: usize = 8;
pub const STRING_DATA_OFFSET_FIELD: usize = 12;
pub const EXTENDED_DATA_OFFSET_FIELD: usize = 16;
pub const DIAGNOSTICS_OFFSET_FIELD: usize = 20;
pub const NODES_OFFSET_FIELD: usize = 24;
pub const NODE_COUNT_FIELD: usize = 28;
pub const SOURCE_TEXT_LENGTH_FIELD: usize = 32;
pub const ROOT_COUNT_FIELD: usize = 36;
pub const SUPPORTED_MAJOR: u8 = 1;
pub const SUPPORTED_MINOR: u8 = 0;
pub const MAJOR_SHIFT: u8 = 4;
pub const MINOR_MASK: u8 = 0x0F;
#[inline]
#[must_use]
pub const fn pack_version(major: u8, minor: u8) -> u8 {
(major << MAJOR_SHIFT) | (minor & MINOR_MASK)
}
#[inline]
#[must_use]
pub const fn major(version_byte: u8) -> u8 {
version_byte >> MAJOR_SHIFT
}
#[inline]
#[must_use]
pub const fn minor(version_byte: u8) -> u8 {
version_byte & MINOR_MASK
}
pub const SUPPORTED_VERSION_BYTE: u8 = pack_version(SUPPORTED_MAJOR, SUPPORTED_MINOR);
pub const COMPAT_MODE_BIT: u8 = 0b0000_0001;
pub const FLAGS_RESERVED_MASK: u8 = 0b1111_1110;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Header {
pub version: u8,
pub flags: u8,
pub root_array_offset: u32,
pub string_offsets_offset: u32,
pub string_data_offset: u32,
pub extended_data_offset: u32,
pub diagnostics_offset: u32,
pub nodes_offset: u32,
pub node_count: u32,
pub source_text_length: u32,
pub root_count: u32,
}
impl Header {
#[inline]
#[must_use]
pub const fn compat_mode(&self) -> bool {
(self.flags & COMPAT_MODE_BIT) != 0
}
#[inline]
#[must_use]
pub const fn major(&self) -> u8 {
major(self.version)
}
#[inline]
#[must_use]
pub const fn minor(&self) -> u8 {
minor(self.version)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_size_is_40_bytes() {
assert_eq!(HEADER_SIZE, 40);
}
#[test]
fn header_field_offsets_cover_40_bytes_without_overlap() {
let offsets: &[(usize, usize)] = &[
(VERSION_OFFSET, 1),
(FLAGS_OFFSET, 1),
(RESERVED_OFFSET, 2),
(ROOT_ARRAY_OFFSET_FIELD, 4),
(STRING_OFFSETS_OFFSET_FIELD, 4),
(STRING_DATA_OFFSET_FIELD, 4),
(EXTENDED_DATA_OFFSET_FIELD, 4),
(DIAGNOSTICS_OFFSET_FIELD, 4),
(NODES_OFFSET_FIELD, 4),
(NODE_COUNT_FIELD, 4),
(SOURCE_TEXT_LENGTH_FIELD, 4),
(ROOT_COUNT_FIELD, 4),
];
let mut cursor = 0usize;
for (offset, size) in offsets {
assert_eq!(*offset, cursor, "field at expected position");
cursor += size;
}
assert_eq!(cursor, HEADER_SIZE, "fields fully cover the header");
}
#[test]
fn version_pack_unpack_roundtrip() {
for major in 0u8..=15 {
for minor in 0u8..=15 {
let byte = pack_version(major, minor);
assert_eq!(super::major(byte), major);
assert_eq!(super::minor(byte), minor);
}
}
}
#[test]
fn supported_version_byte_is_0x10() {
assert_eq!(SUPPORTED_VERSION_BYTE, 0x10);
assert_eq!(major(SUPPORTED_VERSION_BYTE), 1);
assert_eq!(minor(SUPPORTED_VERSION_BYTE), 0);
}
#[test]
fn compat_mode_flag_round_trips() {
let mut h = Header::default();
assert!(!h.compat_mode());
h.flags |= COMPAT_MODE_BIT;
assert!(h.compat_mode());
h.flags |= 0b1010_1010 & FLAGS_RESERVED_MASK;
assert!(h.compat_mode());
}
}