use fhp_core::tag::Tag;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodeId(pub u32);
impl NodeId {
pub const NULL: NodeId = NodeId(u32::MAX);
#[inline(always)]
pub const fn is_null(self) -> bool {
self.0 == u32::MAX
}
#[inline(always)]
pub const fn index(self) -> usize {
self.0 as usize
}
}
impl core::fmt::Debug for NodeId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_null() {
write!(f, "NodeId(NULL)")
} else {
write!(f, "NodeId({})", self.0)
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct NodeFlags(u8);
impl NodeFlags {
pub const IS_VOID: u8 = 1 << 0;
pub const HAS_CHILDREN: u8 = 1 << 1;
pub const HAS_ATTRS: u8 = 1 << 2;
pub const IS_SELF_CLOSING: u8 = 1 << 3;
pub const IS_TEXT: u8 = 1 << 4;
pub const IS_COMMENT: u8 = 1 << 5;
pub const IS_DOCTYPE: u8 = 1 << 6;
pub const IS_CDATA: u8 = 1 << 7;
pub const IS_TEXT_FROM_SOURCE: u8 = 1 << 0;
#[inline(always)]
pub const fn empty() -> Self {
Self(0)
}
#[inline(always)]
pub fn set(&mut self, flag: u8) {
self.0 |= flag;
}
#[inline(always)]
pub const fn has(self, flag: u8) -> bool {
self.0 & flag != 0
}
}
#[repr(C, align(64))]
pub struct Node {
pub tag: Tag,
pub flags: NodeFlags,
pub depth: u16,
pub parent: NodeId,
pub first_child: NodeId,
pub next_sibling: NodeId,
pub last_child: NodeId,
pub prev_sibling: NodeId,
pub text_offset: u32,
pub text_len: u32,
pub attr_offset: u32,
pub attr_raw_offset: u32,
pub class_hash: u64,
pub id_hash: u32,
pub attr_raw_len: u16,
pub element_index: u16,
pub attr_count: u16,
pub _padding: [u8; 6],
}
impl Node {
pub fn new_element(tag: Tag, depth: u16) -> Self {
let mut flags = NodeFlags::empty();
if tag.is_void() {
flags.set(NodeFlags::IS_VOID);
}
Self {
tag,
flags,
depth,
parent: NodeId::NULL,
first_child: NodeId::NULL,
next_sibling: NodeId::NULL,
last_child: NodeId::NULL,
prev_sibling: NodeId::NULL,
text_offset: 0,
text_len: 0,
attr_offset: 0,
attr_count: 0,
attr_raw_offset: 0,
attr_raw_len: 0,
class_hash: 0,
id_hash: 0,
element_index: 0,
_padding: [0; 6],
}
}
pub fn new_text(depth: u16, text_offset: u32, text_len: u32) -> Self {
let mut flags = NodeFlags::empty();
flags.set(NodeFlags::IS_TEXT);
Self {
tag: Tag::Unknown,
flags,
depth,
parent: NodeId::NULL,
first_child: NodeId::NULL,
next_sibling: NodeId::NULL,
last_child: NodeId::NULL,
prev_sibling: NodeId::NULL,
text_offset,
text_len,
attr_offset: 0,
attr_count: 0,
attr_raw_offset: 0,
attr_raw_len: 0,
class_hash: 0,
id_hash: 0,
element_index: 0,
_padding: [0; 6],
}
}
pub fn new_comment(depth: u16, text_offset: u32, text_len: u32) -> Self {
let mut flags = NodeFlags::empty();
flags.set(NodeFlags::IS_COMMENT);
Self {
tag: Tag::Unknown,
flags,
depth,
parent: NodeId::NULL,
first_child: NodeId::NULL,
next_sibling: NodeId::NULL,
last_child: NodeId::NULL,
prev_sibling: NodeId::NULL,
text_offset,
text_len,
attr_offset: 0,
attr_count: 0,
attr_raw_offset: 0,
attr_raw_len: 0,
class_hash: 0,
id_hash: 0,
element_index: 0,
_padding: [0; 6],
}
}
pub fn new_doctype(depth: u16, text_offset: u32, text_len: u32) -> Self {
let mut flags = NodeFlags::empty();
flags.set(NodeFlags::IS_DOCTYPE);
Self {
tag: Tag::Unknown,
flags,
depth,
parent: NodeId::NULL,
first_child: NodeId::NULL,
next_sibling: NodeId::NULL,
last_child: NodeId::NULL,
prev_sibling: NodeId::NULL,
text_offset,
text_len,
attr_offset: 0,
attr_count: 0,
attr_raw_offset: 0,
attr_raw_len: 0,
class_hash: 0,
id_hash: 0,
element_index: 0,
_padding: [0; 6],
}
}
}
impl core::fmt::Debug for Node {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Node")
.field("tag", &self.tag)
.field("flags", &self.flags)
.field("depth", &self.depth)
.field("parent", &self.parent)
.field("first_child", &self.first_child)
.field("next_sibling", &self.next_sibling)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn node_is_64_bytes() {
assert_eq!(
std::mem::size_of::<Node>(),
64,
"Node must be exactly 64 bytes (one cache line)"
);
}
#[test]
fn node_alignment_is_64() {
assert_eq!(
std::mem::align_of::<Node>(),
64,
"Node must be 64-byte aligned"
);
}
#[test]
fn node_id_null() {
assert!(NodeId::NULL.is_null());
assert!(!NodeId(0).is_null());
assert!(!NodeId(42).is_null());
}
#[test]
fn node_id_debug() {
assert_eq!(format!("{:?}", NodeId::NULL), "NodeId(NULL)");
assert_eq!(format!("{:?}", NodeId(5)), "NodeId(5)");
}
#[test]
fn node_flags() {
let mut flags = NodeFlags::empty();
assert!(!flags.has(NodeFlags::IS_VOID));
assert!(!flags.has(NodeFlags::HAS_CHILDREN));
flags.set(NodeFlags::IS_VOID);
assert!(flags.has(NodeFlags::IS_VOID));
assert!(!flags.has(NodeFlags::HAS_CHILDREN));
flags.set(NodeFlags::HAS_CHILDREN);
assert!(flags.has(NodeFlags::IS_VOID));
assert!(flags.has(NodeFlags::HAS_CHILDREN));
}
#[test]
fn new_element_sets_void_flag() {
let br = Node::new_element(Tag::Br, 0);
assert!(br.flags.has(NodeFlags::IS_VOID));
let div = Node::new_element(Tag::Div, 0);
assert!(!div.flags.has(NodeFlags::IS_VOID));
}
#[test]
fn new_text_sets_text_flag() {
let text = Node::new_text(1, 0, 5);
assert!(text.flags.has(NodeFlags::IS_TEXT));
assert_eq!(text.text_offset, 0);
assert_eq!(text.text_len, 5);
}
}