use crate::extension::ContainerKind;
use aozora_spec::{NormalizedOffset, Sentinel};
use aozora_veb::EytzingerMap;
use super::types::AozoraNode;
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum NodeRef<'src> {
Inline(AozoraNode<'src>),
BlockLeaf(AozoraNode<'src>),
BlockOpen(ContainerKind),
BlockClose(ContainerKind),
}
impl NodeRef<'_> {
#[must_use]
pub const fn sentinel_kind(self) -> Sentinel {
match self {
Self::Inline(_) => Sentinel::Inline,
Self::BlockLeaf(_) => Sentinel::BlockLeaf,
Self::BlockOpen(_) => Sentinel::BlockOpen,
Self::BlockClose(_) => Sentinel::BlockClose,
}
}
#[must_use]
pub const fn kind(self) -> crate::NodeKind {
match self {
Self::Inline(node) | Self::BlockLeaf(node) => node.kind(),
Self::BlockOpen(_) => crate::NodeKind::ContainerOpen,
Self::BlockClose(_) => crate::NodeKind::ContainerClose,
}
}
}
#[derive(Debug, Clone)]
pub struct Registry<'src> {
table: EytzingerMap<u32, NodeRef<'src>>,
}
impl<'src> Registry<'src> {
#[must_use]
pub fn from_sorted_slice(entries: &[(u32, NodeRef<'src>)]) -> Self {
Self {
table: EytzingerMap::from_sorted_slice(entries),
}
}
#[must_use]
pub const fn empty() -> Self {
Self {
table: EytzingerMap::new(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.table.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.table.len()
}
#[must_use]
pub fn node_at(&self, pos: NormalizedOffset) -> Option<NodeRef<'src>> {
self.table.get(&pos.get()).copied()
}
pub fn iter_sorted(&self) -> impl Iterator<Item = (u32, NodeRef<'src>)> + '_ {
self.table.iter_sorted().map(|(&p, &nr)| (p, nr))
}
pub fn iter_kind(&self, kind: Sentinel) -> impl Iterator<Item = (u32, NodeRef<'src>)> + '_ {
self.iter_sorted()
.filter(move |(_, nr)| nr.sentinel_kind() == kind)
}
#[must_use]
pub fn count_kind(&self, kind: Sentinel) -> usize {
self.iter_kind(kind).count()
}
}
impl Default for Registry<'_> {
fn default() -> Self {
Self::empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ContainerPair {
pub kind: ContainerKind,
pub open: NormalizedOffset,
pub close: NormalizedOffset,
}
impl ContainerPair {
#[must_use]
pub const fn new(kind: ContainerKind, open: NormalizedOffset, close: NormalizedOffset) -> Self {
Self { kind, open, close }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Indent;
#[test]
fn empty_registry_reports_empty() {
let r: Registry<'static> = Registry::empty();
assert!(r.is_empty());
assert_eq!(r.len(), 0);
}
#[test]
fn default_registry_is_empty() {
let r: Registry<'static> = Registry::default();
assert!(r.is_empty());
}
#[test]
fn node_at_returns_inline_payload_for_inline_sentinel_position() {
let r: Registry<'static> = Registry::from_sorted_slice(&[
(
10u32,
NodeRef::Inline(AozoraNode::Indent(Indent { amount: 1 })),
),
(20u32, NodeRef::Inline(AozoraNode::PageBreak)),
(
30u32,
NodeRef::Inline(AozoraNode::Indent(Indent { amount: 3 })),
),
]);
assert!(!r.is_empty());
assert_eq!(r.len(), 3);
let got = r.node_at(NormalizedOffset::new(20));
assert!(matches!(got, Some(NodeRef::Inline(AozoraNode::PageBreak))));
assert!(r.node_at(NormalizedOffset::new(15)).is_none());
}
#[test]
fn node_at_dispatches_to_correct_variant() {
let r: Registry<'static> = Registry::from_sorted_slice(&[
(10u32, NodeRef::Inline(AozoraNode::PageBreak)),
(20u32, NodeRef::BlockLeaf(AozoraNode::PageBreak)),
(30u32, NodeRef::BlockOpen(ContainerKind::Keigakomi)),
(40u32, NodeRef::BlockClose(ContainerKind::Keigakomi)),
]);
assert!(matches!(
r.node_at(NormalizedOffset::new(10)),
Some(NodeRef::Inline(AozoraNode::PageBreak))
));
assert!(matches!(
r.node_at(NormalizedOffset::new(20)),
Some(NodeRef::BlockLeaf(AozoraNode::PageBreak))
));
assert!(matches!(
r.node_at(NormalizedOffset::new(30)),
Some(NodeRef::BlockOpen(ContainerKind::Keigakomi))
));
assert!(matches!(
r.node_at(NormalizedOffset::new(40)),
Some(NodeRef::BlockClose(ContainerKind::Keigakomi))
));
assert!(r.node_at(NormalizedOffset::new(99)).is_none());
}
#[test]
fn count_kind_buckets_entries_by_sentinel() {
let r: Registry<'static> = Registry::from_sorted_slice(&[
(
5u32,
NodeRef::BlockOpen(ContainerKind::Indent { amount: 2 }),
),
(10u32, NodeRef::BlockOpen(ContainerKind::Keigakomi)),
(15u32, NodeRef::Inline(AozoraNode::PageBreak)),
(20u32, NodeRef::BlockClose(ContainerKind::Keigakomi)),
]);
assert_eq!(r.count_kind(Sentinel::BlockOpen), 2);
assert_eq!(r.count_kind(Sentinel::Inline), 1);
assert_eq!(r.count_kind(Sentinel::BlockClose), 1);
assert_eq!(r.count_kind(Sentinel::BlockLeaf), 0);
}
#[test]
fn node_ref_sentinel_kind_round_trips() {
let inline = NodeRef::Inline(AozoraNode::PageBreak);
let block_leaf = NodeRef::BlockLeaf(AozoraNode::PageBreak);
let block_open = NodeRef::BlockOpen(ContainerKind::Keigakomi);
let block_close = NodeRef::BlockClose(ContainerKind::Keigakomi);
assert_eq!(inline.sentinel_kind(), Sentinel::Inline);
assert_eq!(block_leaf.sentinel_kind(), Sentinel::BlockLeaf);
assert_eq!(block_open.sentinel_kind(), Sentinel::BlockOpen);
assert_eq!(block_close.sentinel_kind(), Sentinel::BlockClose);
}
}