use std::num::NonZeroU32;
use std::sync::atomic::{AtomicU32, Ordering};
pub trait HasNodeIndex {
fn node_index(&self) -> &AtomicNodeIndex;
}
impl<T> HasNodeIndex for &T
where
T: HasNodeIndex,
{
fn node_index(&self) -> &AtomicNodeIndex {
T::node_index(*self)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
pub struct NodeIndex(NonZeroU32);
impl NodeIndex {
pub const NONE: NodeIndex = NodeIndex(NonZeroU32::new(NodeIndex::_NONE).unwrap());
const _NONE: u32 = u32::MAX - 1;
pub fn as_u32(self) -> Option<u32> {
if self == NodeIndex::NONE {
None
} else {
Some(self.0.get() - 1)
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum NodeIndexError {
NoParent,
TooNested,
ExhaustedSubIndices,
ExhaustedSubSubIndices,
OverflowedIndices,
OverflowedSubIndices,
}
const MAX_LEVEL: u32 = 2;
const LEVEL_BITS: u32 = 32 - MAX_LEVEL.leading_zeros();
const LEVEL_SHIFT: u32 = 32 - LEVEL_BITS;
const LEVEL_MASK: u32 = ((LEVEL_BITS << 1) - 1) << LEVEL_SHIFT;
const SUB_NODES: u32 = 256;
const SUB_SUB_NODES: u32 = 8;
pub const MAX_REAL_INDEX: u32 = (1 << LEVEL_SHIFT) - 1;
pub fn sub_ast_level(index: u32) -> u32 {
(index & LEVEL_MASK) >> LEVEL_SHIFT
}
pub fn sub_indices(index: u32) -> Result<(u32, u32), NodeIndexError> {
let level = sub_ast_level(index);
if level >= MAX_LEVEL {
return Err(NodeIndexError::TooNested);
}
let next_level = (level + 1) << LEVEL_SHIFT;
let without_level = index & !LEVEL_MASK;
let (nodes_in_level, error_kind) = if level == 0 {
(SUB_NODES, NodeIndexError::OverflowedIndices)
} else if level == 1 {
(SUB_SUB_NODES, NodeIndexError::OverflowedSubIndices)
} else {
unreachable!(
"Someone made a mistake updating the encoding of node indices: {index:08X} had level {level}"
);
};
let sub_index_without_level = without_level
.checked_mul(nodes_in_level)
.ok_or(error_kind)?;
if sub_index_without_level > MAX_REAL_INDEX {
return Err(error_kind);
}
let first_index = sub_index_without_level | next_level;
let last_index = first_index + nodes_in_level - 1;
Ok((first_index, last_index))
}
impl From<u32> for NodeIndex {
fn from(value: u32) -> Self {
match NonZeroU32::new(value + 1).map(NodeIndex) {
None | Some(NodeIndex::NONE) => panic!("exceeded maximum `NodeIndex`"),
Some(index) => index,
}
}
}
impl std::fmt::Debug for NodeIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if *self == Self::NONE {
f.debug_tuple("NodeIndex(None)").finish()
} else {
f.debug_tuple("NodeIndex").field(&self.0).finish()
}
}
}
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
pub struct AtomicNodeIndex(AtomicU32);
#[allow(clippy::declare_interior_mutable_const)]
impl AtomicNodeIndex {
pub const NONE: AtomicNodeIndex = AtomicNodeIndex(AtomicU32::new(NodeIndex::_NONE));
pub fn load(&self) -> NodeIndex {
let index = NonZeroU32::new(self.0.load(Ordering::Relaxed))
.expect("value stored was a valid `NodeIndex`");
NodeIndex(index)
}
pub fn set(&self, index: NodeIndex) {
self.0.store(index.0.get(), Ordering::Relaxed);
}
}
impl Default for AtomicNodeIndex {
fn default() -> Self {
Self::NONE
}
}
impl std::fmt::Debug for AtomicNodeIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.load(), f)
}
}
impl std::hash::Hash for AtomicNodeIndex {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.load().hash(state);
}
}
impl PartialOrd for AtomicNodeIndex {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AtomicNodeIndex {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.load().cmp(&other.load())
}
}
impl Eq for AtomicNodeIndex {}
impl PartialEq for AtomicNodeIndex {
fn eq(&self, other: &Self) -> bool {
self.load() == other.load()
}
}
impl Clone for AtomicNodeIndex {
fn clone(&self) -> Self {
Self(AtomicU32::from(self.0.load(Ordering::Relaxed)))
}
}
#[cfg(test)]
mod tests {
use super::{AtomicNodeIndex, NodeIndex};
#[test]
fn test_node_index() {
let index = AtomicNodeIndex::NONE;
assert_eq!(index.load(), NodeIndex::NONE);
assert_eq!(format!("{index:?}"), "NodeIndex(None)");
index.set(NodeIndex::from(1));
assert_eq!(index.load(), NodeIndex::from(1));
assert_eq!(index.load().as_u32(), Some(1));
let index = NodeIndex::from(0);
assert_eq!(index.as_u32(), Some(0));
let index = NodeIndex::NONE;
assert_eq!(index.as_u32(), None);
}
}