use std::collections::BTreeSet;
use std::rc::Rc;
use crate::errors::{Error, Result};
use crate::property_info_parser::*;
use crate::trie_builder::*;
use crate::trie_node_arena::TrieNodeArena;
pub(crate) struct TrieSerializer {
arena: TrieNodeArena,
}
fn resolve_index<F>(name: Option<&str>, mut lookup: F) -> Result<u32>
where
F: FnMut(&str) -> Option<usize>,
{
match name {
Some(s) if !s.is_empty() => match lookup(s) {
Some(i) => u32::try_from(i)
.ok()
.filter(|&v| v != NO_INDEX)
.ok_or_else(|| {
Error::FileValidation(format!(
"String table index {i} does not fit below the u32::MAX sentinel"
))
}),
None => Err(Error::FileValidation(format!(
"'{s}' missing from the serialized string table (serializer invariant violation)"
))),
},
_ => Ok(NO_INDEX),
}
}
impl TrieSerializer {
pub(crate) fn new(trie_builder: &TrieBuilder) -> Result<Self> {
let mut this = Self {
arena: TrieNodeArena::new(),
};
let header_offset = this.arena.allocate_object::<PropertyInfoAreaHeader>()? as usize;
{
let header = this
.arena
.get_object::<PropertyInfoAreaHeader>(header_offset)?;
header.current_version = 1;
header.minimum_supported_version = 1;
}
this.arena
.get_object::<PropertyInfoAreaHeader>(header_offset)?
.contexts_offset = this.arena.size() as u32;
this.serialize_strings(&trie_builder.contexts)?;
this.arena
.get_object::<PropertyInfoAreaHeader>(header_offset)?
.types_offset = this.arena.size() as u32;
this.serialize_strings(&trie_builder.types)?;
this.arena
.get_object::<PropertyInfoAreaHeader>(header_offset)?
.size = this.arena.size() as u32;
let root_trie_offset = this.write_trie_node(&trie_builder.root, 0)?;
this.arena
.get_object::<PropertyInfoAreaHeader>(header_offset)?
.root_offset = root_trie_offset;
let final_size = this.arena.size() as u32; this.arena
.get_object::<PropertyInfoAreaHeader>(header_offset)?
.size = final_size;
Ok(this)
}
fn write_property_entry(&mut self, property_entry: &PropertyEntryBuilder) -> Result<u32> {
let context_index = resolve_index(property_entry.context.as_deref(), |s| {
self.arena.info().find_context_index(s)
})?;
let type_index = resolve_index(property_entry.rtype.as_deref(), |s| {
self.arena.info().find_type_index(s)
})?;
let entry_offset = self.arena.allocate_object::<PropertyEntry>()?;
let name_offset = self.arena.allocate_and_write_string(&property_entry.name)?;
let namelen = u32::try_from(property_entry.name.len()).map_err(|_| {
Error::FileValidation(format!(
"Property name too long: {} bytes",
property_entry.name.len()
))
})?;
let entry = self
.arena
.get_object::<PropertyEntry>(entry_offset as usize)?;
entry.name_offset = name_offset;
entry.namelen = namelen;
entry.context_index = context_index;
entry.type_index = type_index;
Ok(entry_offset)
}
fn write_trie_node(&mut self, builder_node: &TrieBuilderNode, depth: usize) -> Result<u32> {
const MAX_TRIE_DEPTH: usize = 512;
if depth > MAX_TRIE_DEPTH {
return Err(Error::FileValidation(format!(
"Trie deeper than {MAX_TRIE_DEPTH} levels — refusing to serialize"
)));
}
let trie_offset = self.arena.allocate_object::<TrieNodeData>()? as usize;
let property_entry = self.write_property_entry(&builder_node.property_entry)?;
self.arena
.get_object::<TrieNodeData>(trie_offset)?
.property_entry = property_entry;
let mut sorted_prefix_matches: Vec<_> = builder_node.prefixes.iter().collect();
sorted_prefix_matches.sort_by(|a, b| {
b.name
.len()
.cmp(&a.name.len())
.then_with(|| a.name.cmp(&b.name))
});
self.arena
.get_object::<TrieNodeData>(trie_offset)?
.num_prefixes = sorted_prefix_matches.len() as u32;
let prefix_entries_array_offset = self
.arena
.allocate_uint32_array(sorted_prefix_matches.len())?;
self.arena
.get_object::<TrieNodeData>(trie_offset)?
.prefix_entries = prefix_entries_array_offset;
let prefix_offsets = sorted_prefix_matches
.iter()
.map(|e| self.write_property_entry(e))
.collect::<Result<Vec<u32>>>()?;
self.arena
.uint32_array(prefix_entries_array_offset as usize, prefix_offsets.len())?
.copy_from_slice(&prefix_offsets);
let mut sorted_exact_matches: Vec<_> = builder_node.exact_matches.iter().collect();
sorted_exact_matches.sort_by(|a, b| a.name.cmp(&b.name));
self.arena
.get_object::<TrieNodeData>(trie_offset)?
.num_exact_matches = sorted_exact_matches.len() as u32;
let exact_match_entries_array_offset = self
.arena
.allocate_uint32_array(sorted_exact_matches.len())?;
self.arena
.get_object::<TrieNodeData>(trie_offset)?
.exact_match_entries = exact_match_entries_array_offset;
let exact_offsets = sorted_exact_matches
.iter()
.map(|e| self.write_property_entry(e))
.collect::<Result<Vec<u32>>>()?;
self.arena
.uint32_array(
exact_match_entries_array_offset as usize,
exact_offsets.len(),
)?
.copy_from_slice(&exact_offsets);
let mut sorted_children: Vec<_> = builder_node.children.values().collect();
sorted_children.sort_by(|a, b| a.property_entry.name.cmp(&b.property_entry.name));
self.arena
.get_object::<TrieNodeData>(trie_offset)?
.num_child_nodes = sorted_children.len() as u32;
let children_offset_array_offset =
self.arena.allocate_uint32_array(sorted_children.len())?;
self.arena
.get_object::<TrieNodeData>(trie_offset)?
.child_nodes = children_offset_array_offset;
let child_offsets = sorted_children
.iter()
.map(|child| self.write_trie_node(child, depth + 1))
.collect::<Result<Vec<u32>>>()?;
self.arena
.uint32_array(children_offset_array_offset as usize, child_offsets.len())?
.copy_from_slice(&child_offsets);
Ok(trie_offset as u32)
}
fn serialize_strings(&mut self, strings: &BTreeSet<Rc<str>>) -> Result<()> {
self.arena.allocate_and_write_uint32(strings.len() as u32)?;
let n = strings.len();
let offset_array_offset = self.arena.allocate_uint32_array(n)?;
let offsets = strings
.iter()
.map(|s| self.arena.allocate_and_write_string(s))
.collect::<Result<Vec<u32>>>()?;
self.arena
.uint32_array(offset_array_offset as usize, n)?
.copy_from_slice(&offsets);
Ok(())
}
pub(crate) fn into_data(self) -> Vec<u8> {
self.arena.into_data()
}
}