use core::num::NonZeroU32;
use oxc_allocator::{Allocator, Vec as ArenaVec};
use crate::format::extended_data::EXTENDED_DATA_ALIGNMENT;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtOffset(NonZeroU32);
impl ExtOffset {
#[inline]
#[must_use]
pub const fn from_u32(offset: u32) -> Option<Self> {
match NonZeroU32::new(offset.wrapping_add(1)) {
Some(nz) => Some(ExtOffset(nz)),
None => None,
}
}
#[inline]
#[must_use]
pub const fn as_u32(self) -> u32 {
self.0.get() - 1
}
}
pub struct ExtendedDataBuilder<'arena> {
pub(crate) buffer: ArenaVec<'arena, u8>,
}
impl<'arena> ExtendedDataBuilder<'arena> {
#[must_use]
pub fn new(arena: &'arena Allocator) -> Self {
ExtendedDataBuilder {
buffer: ArenaVec::new_in(arena),
}
}
pub(crate) fn reset(&mut self) {
self.buffer.truncate(0);
}
pub fn reserve(&mut self, size: usize) -> ExtOffset {
let aligned_offset = self.next_aligned_offset();
let new_len = aligned_offset + size;
self.buffer.resize(new_len, 0);
ExtOffset::from_u32(aligned_offset as u32).expect("Extended Data offset overflow")
}
#[inline]
pub fn slice_mut(&mut self, offset: ExtOffset, len: usize) -> &mut [u8] {
let start = offset.as_u32() as usize;
&mut self.buffer[start..start + len]
}
#[inline]
pub fn reserve_mut(&mut self, size: usize) -> (ExtOffset, &mut [u8]) {
let aligned_offset = self.next_aligned_offset();
let new_len = aligned_offset + size;
self.buffer.resize(new_len, 0);
let off =
ExtOffset::from_u32(aligned_offset as u32).expect("Extended Data offset overflow");
let slice = &mut self.buffer[aligned_offset..new_len];
(off, slice)
}
#[inline]
#[must_use]
pub fn size(&self) -> usize {
self.buffer.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
#[inline]
fn next_aligned_offset(&self) -> usize {
const _: () = assert!(EXTENDED_DATA_ALIGNMENT == 8);
(self.buffer.len() + 7) & !7
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ext_offset_round_trips() {
for raw in [0u32, 1, 7, 8, 0x3FFF_FFFE] {
let off = ExtOffset::from_u32(raw).unwrap();
assert_eq!(off.as_u32(), raw);
}
}
#[test]
fn ext_offset_zero_is_representable() {
let off = ExtOffset::from_u32(0).unwrap();
assert_eq!(off.as_u32(), 0);
}
#[test]
fn ext_offset_rejects_u32_max() {
assert!(ExtOffset::from_u32(u32::MAX).is_none());
}
#[test]
fn reserve_first_record_starts_at_zero() {
let arena = Allocator::default();
let mut b = ExtendedDataBuilder::new(&arena);
let off = b.reserve(2);
assert_eq!(off.as_u32(), 0);
assert_eq!(b.size(), 2);
}
#[test]
fn reserve_pads_to_8_byte_alignment() {
let arena = Allocator::default();
let mut b = ExtendedDataBuilder::new(&arena);
let _ = b.reserve(2);
let off = b.reserve(2);
assert_eq!(off.as_u32(), 8);
assert_eq!(b.size(), 10);
}
#[test]
fn slice_mut_round_trip() {
let arena = Allocator::default();
let mut b = ExtendedDataBuilder::new(&arena);
let off = b.reserve(4);
b.slice_mut(off, 4).copy_from_slice(&[1, 2, 3, 4]);
assert_eq!(&b.buffer[..], &[1, 2, 3, 4]);
}
}