use crate::format::header::{
self, COMPAT_MODE_BIT, DIAGNOSTICS_OFFSET_FIELD, EXTENDED_DATA_OFFSET_FIELD, FLAGS_OFFSET,
HEADER_SIZE, NODE_COUNT_FIELD, NODES_OFFSET_FIELD, ROOT_ARRAY_OFFSET_FIELD, ROOT_COUNT_FIELD,
STRING_DATA_OFFSET_FIELD, STRING_OFFSETS_OFFSET_FIELD,
};
use crate::format::node_record::STRING_PAYLOAD_NONE_SENTINEL;
use crate::format::root_index::{
BASE_OFFSET_FIELD, NODE_INDEX_OFFSET, ROOT_INDEX_ENTRY_SIZE, SOURCE_OFFSET_FIELD,
};
use crate::format::string_field::StringField;
use crate::format::string_table::STRING_OFFSET_ENTRY_SIZE;
use super::error::DecodeError;
use super::helpers::read_u32;
use super::nodes::LazyNode;
use super::nodes::comment_ast::LazyJsdocBlock;
#[derive(Debug, Clone, Copy)]
pub struct LazySourceFile<'a> {
pub(crate) bytes: &'a [u8],
pub compat_mode: bool,
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 root_count: u32,
}
impl<'a> LazySourceFile<'a> {
pub fn new(bytes: &'a [u8]) -> Result<Self, DecodeError> {
if bytes.len() < HEADER_SIZE {
return Err(DecodeError::TooShort {
actual: bytes.len(),
required: HEADER_SIZE,
});
}
let version_byte = bytes[0];
let major = header::major(version_byte);
if major != header::SUPPORTED_MAJOR {
return Err(DecodeError::IncompatibleMajor {
buffer_major: major,
decoder_major: header::SUPPORTED_MAJOR,
});
}
let flags = bytes[FLAGS_OFFSET];
Ok(LazySourceFile {
bytes,
compat_mode: (flags & COMPAT_MODE_BIT) != 0,
root_array_offset: read_u32(bytes, ROOT_ARRAY_OFFSET_FIELD),
string_offsets_offset: read_u32(bytes, STRING_OFFSETS_OFFSET_FIELD),
string_data_offset: read_u32(bytes, STRING_DATA_OFFSET_FIELD),
extended_data_offset: read_u32(bytes, EXTENDED_DATA_OFFSET_FIELD),
diagnostics_offset: read_u32(bytes, DIAGNOSTICS_OFFSET_FIELD),
nodes_offset: read_u32(bytes, NODES_OFFSET_FIELD),
node_count: read_u32(bytes, NODE_COUNT_FIELD),
root_count: read_u32(bytes, ROOT_COUNT_FIELD),
})
}
#[inline]
#[must_use]
pub const fn bytes(&self) -> &'a [u8] {
self.bytes
}
#[must_use]
pub fn get_string(&self, idx: u32) -> Option<&'a str> {
if idx == STRING_PAYLOAD_NONE_SENTINEL {
return None;
}
let so_offset =
self.string_offsets_offset as usize + idx as usize * STRING_OFFSET_ENTRY_SIZE;
let start = read_u32(self.bytes, so_offset) as usize;
let end = read_u32(self.bytes, so_offset + 4) as usize;
let sd_offset = self.string_data_offset as usize;
let slice = &self.bytes[sd_offset + start..sd_offset + end];
Some(unsafe { core::str::from_utf8_unchecked(slice) })
}
#[inline]
#[must_use]
pub fn get_inline_string(&self, offset: u32, length: u8) -> &'a str {
let sd_offset = self.string_data_offset as usize;
let start = sd_offset + offset as usize;
let end = start + length as usize;
let slice = &self.bytes[start..end];
unsafe { core::str::from_utf8_unchecked(slice) }
}
#[inline]
#[must_use]
pub fn get_string_by_field(&self, field: StringField) -> Option<&'a str> {
if field.is_none() {
return None;
}
let sd_offset = self.string_data_offset as usize;
let start = sd_offset + field.offset as usize;
let end = start + field.length as usize;
let slice = &self.bytes[start..end];
Some(unsafe { core::str::from_utf8_unchecked(slice) })
}
#[inline]
#[must_use]
pub fn get_required_string_by_field(&self, field: StringField) -> &'a str {
debug_assert!(
!field.is_none(),
"get_required_string_by_field called with StringField::NONE"
);
let sd_offset = self.string_data_offset as usize;
let start = sd_offset + field.offset as usize;
let end = start + field.length as usize;
let slice = &self.bytes[start..end];
unsafe { core::str::from_utf8_unchecked(slice) }
}
#[must_use]
pub fn get_root_base_offset(&self, root_index: u32) -> u32 {
let off = self.root_array_offset as usize
+ root_index as usize * ROOT_INDEX_ENTRY_SIZE
+ BASE_OFFSET_FIELD;
read_u32(self.bytes, off)
}
#[must_use]
pub fn get_root_source_offset_in_data(&self, root_index: u32) -> u32 {
let off = self.root_array_offset as usize
+ root_index as usize * ROOT_INDEX_ENTRY_SIZE
+ SOURCE_OFFSET_FIELD;
read_u32(self.bytes, off)
}
#[must_use]
pub fn slice_source_text(&self, root_index: u32, start: u32, end: u32) -> Option<&'a str> {
if start == 0 && end == 0 {
return None;
}
if start > end {
return None;
}
let source_offset = self.get_root_source_offset_in_data(root_index) as usize;
let sd_offset = self.string_data_offset as usize;
let abs_start = sd_offset + source_offset + start as usize;
let abs_end = sd_offset + source_offset + end as usize;
if abs_end > self.bytes.len() {
return None;
}
let slice = &self.bytes[abs_start..abs_end];
Some(unsafe { core::str::from_utf8_unchecked(slice) })
}
pub fn asts(&'a self) -> AstsIter<'a> {
AstsIter {
source_file: self,
cursor: 0,
}
}
}
#[derive(Debug)]
pub struct AstsIter<'a> {
source_file: &'a LazySourceFile<'a>,
cursor: u32,
}
impl<'a> Iterator for AstsIter<'a> {
type Item = Option<LazyJsdocBlock<'a>>;
fn next(&mut self) -> Option<Self::Item> {
if self.cursor >= self.source_file.root_count {
return None;
}
let root_index = self.cursor;
let off = self.source_file.root_array_offset as usize
+ root_index as usize * ROOT_INDEX_ENTRY_SIZE
+ NODE_INDEX_OFFSET;
let node_index = read_u32(self.source_file.bytes, off);
self.cursor += 1;
if node_index == 0 {
Some(None)
} else {
Some(Some(LazyJsdocBlock::from_index(
self.source_file,
node_index,
root_index,
)))
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.source_file.root_count - self.cursor) as usize;
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for AstsIter<'_> {}
#[cfg(test)]
mod tests {
use super::*;
use core::mem::size_of;
#[test]
fn lazy_source_file_is_compact() {
assert!(size_of::<LazySourceFile<'static>>() <= 64);
}
}