1use fhp_core::tag::Tag;
8
9#[derive(Clone, Copy, PartialEq, Eq, Hash)]
14pub struct NodeId(pub u32);
15
16impl NodeId {
17 pub const NULL: NodeId = NodeId(u32::MAX);
19
20 #[inline(always)]
22 pub const fn is_null(self) -> bool {
23 self.0 == u32::MAX
24 }
25
26 #[inline(always)]
28 pub const fn index(self) -> usize {
29 self.0 as usize
30 }
31}
32
33impl core::fmt::Debug for NodeId {
34 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
35 if self.is_null() {
36 write!(f, "NodeId(NULL)")
37 } else {
38 write!(f, "NodeId({})", self.0)
39 }
40 }
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
47pub struct NodeFlags(u8);
48
49impl NodeFlags {
50 pub const IS_VOID: u8 = 1 << 0;
52 pub const HAS_CHILDREN: u8 = 1 << 1;
54 pub const HAS_ATTRS: u8 = 1 << 2;
56 pub const IS_SELF_CLOSING: u8 = 1 << 3;
58 pub const IS_TEXT: u8 = 1 << 4;
60 pub const IS_COMMENT: u8 = 1 << 5;
62 pub const IS_DOCTYPE: u8 = 1 << 6;
64 pub const IS_CDATA: u8 = 1 << 7;
66
67 pub const IS_TEXT_FROM_SOURCE: u8 = 1 << 0;
72
73 #[inline(always)]
75 pub const fn empty() -> Self {
76 Self(0)
77 }
78
79 #[inline(always)]
81 pub fn set(&mut self, flag: u8) {
82 self.0 |= flag;
83 }
84
85 #[inline(always)]
87 pub const fn has(self, flag: u8) -> bool {
88 self.0 & flag != 0
89 }
90}
91
92#[repr(C, align(64))]
121pub struct Node {
122 pub tag: Tag,
125 pub flags: NodeFlags,
127 pub depth: u16,
129 pub parent: NodeId,
131 pub first_child: NodeId,
133 pub next_sibling: NodeId,
135 pub last_child: NodeId,
137 pub prev_sibling: NodeId,
139 pub text_offset: u32,
141 pub text_len: u32,
143
144 pub attr_offset: u32,
149 pub attr_raw_offset: u32,
152 pub class_hash: u64,
159 pub id_hash: u32,
164 pub attr_raw_len: u16,
167 pub element_index: u16,
169 pub attr_count: u16,
171 pub _padding: [u8; 6],
173}
174
175impl Node {
176 pub fn new_element(tag: Tag, depth: u16) -> Self {
178 let mut flags = NodeFlags::empty();
179 if tag.is_void() {
180 flags.set(NodeFlags::IS_VOID);
181 }
182 Self {
183 tag,
184 flags,
185 depth,
186 parent: NodeId::NULL,
187 first_child: NodeId::NULL,
188 next_sibling: NodeId::NULL,
189 last_child: NodeId::NULL,
190 prev_sibling: NodeId::NULL,
191 text_offset: 0,
192 text_len: 0,
193 attr_offset: 0,
194 attr_count: 0,
195 attr_raw_offset: 0,
196 attr_raw_len: 0,
197 class_hash: 0,
198 id_hash: 0,
199 element_index: 0,
200 _padding: [0; 6],
201 }
202 }
203
204 pub fn new_text(depth: u16, text_offset: u32, text_len: u32) -> Self {
206 let mut flags = NodeFlags::empty();
207 flags.set(NodeFlags::IS_TEXT);
208 Self {
209 tag: Tag::Unknown,
210 flags,
211 depth,
212 parent: NodeId::NULL,
213 first_child: NodeId::NULL,
214 next_sibling: NodeId::NULL,
215 last_child: NodeId::NULL,
216 prev_sibling: NodeId::NULL,
217 text_offset,
218 text_len,
219 attr_offset: 0,
220 attr_count: 0,
221 attr_raw_offset: 0,
222 attr_raw_len: 0,
223 class_hash: 0,
224 id_hash: 0,
225 element_index: 0,
226 _padding: [0; 6],
227 }
228 }
229
230 pub fn new_comment(depth: u16, text_offset: u32, text_len: u32) -> Self {
232 let mut flags = NodeFlags::empty();
233 flags.set(NodeFlags::IS_COMMENT);
234 Self {
235 tag: Tag::Unknown,
236 flags,
237 depth,
238 parent: NodeId::NULL,
239 first_child: NodeId::NULL,
240 next_sibling: NodeId::NULL,
241 last_child: NodeId::NULL,
242 prev_sibling: NodeId::NULL,
243 text_offset,
244 text_len,
245 attr_offset: 0,
246 attr_count: 0,
247 attr_raw_offset: 0,
248 attr_raw_len: 0,
249 class_hash: 0,
250 id_hash: 0,
251 element_index: 0,
252 _padding: [0; 6],
253 }
254 }
255
256 pub fn new_doctype(depth: u16, text_offset: u32, text_len: u32) -> Self {
258 let mut flags = NodeFlags::empty();
259 flags.set(NodeFlags::IS_DOCTYPE);
260 Self {
261 tag: Tag::Unknown,
262 flags,
263 depth,
264 parent: NodeId::NULL,
265 first_child: NodeId::NULL,
266 next_sibling: NodeId::NULL,
267 last_child: NodeId::NULL,
268 prev_sibling: NodeId::NULL,
269 text_offset,
270 text_len,
271 attr_offset: 0,
272 attr_count: 0,
273 attr_raw_offset: 0,
274 attr_raw_len: 0,
275 class_hash: 0,
276 id_hash: 0,
277 element_index: 0,
278 _padding: [0; 6],
279 }
280 }
281}
282
283impl core::fmt::Debug for Node {
284 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
285 f.debug_struct("Node")
286 .field("tag", &self.tag)
287 .field("flags", &self.flags)
288 .field("depth", &self.depth)
289 .field("parent", &self.parent)
290 .field("first_child", &self.first_child)
291 .field("next_sibling", &self.next_sibling)
292 .finish()
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn node_is_64_bytes() {
302 assert_eq!(
303 std::mem::size_of::<Node>(),
304 64,
305 "Node must be exactly 64 bytes (one cache line)"
306 );
307 }
308
309 #[test]
310 fn node_alignment_is_64() {
311 assert_eq!(
312 std::mem::align_of::<Node>(),
313 64,
314 "Node must be 64-byte aligned"
315 );
316 }
317
318 #[test]
319 fn node_id_null() {
320 assert!(NodeId::NULL.is_null());
321 assert!(!NodeId(0).is_null());
322 assert!(!NodeId(42).is_null());
323 }
324
325 #[test]
326 fn node_id_debug() {
327 assert_eq!(format!("{:?}", NodeId::NULL), "NodeId(NULL)");
328 assert_eq!(format!("{:?}", NodeId(5)), "NodeId(5)");
329 }
330
331 #[test]
332 fn node_flags() {
333 let mut flags = NodeFlags::empty();
334 assert!(!flags.has(NodeFlags::IS_VOID));
335 assert!(!flags.has(NodeFlags::HAS_CHILDREN));
336
337 flags.set(NodeFlags::IS_VOID);
338 assert!(flags.has(NodeFlags::IS_VOID));
339 assert!(!flags.has(NodeFlags::HAS_CHILDREN));
340
341 flags.set(NodeFlags::HAS_CHILDREN);
342 assert!(flags.has(NodeFlags::IS_VOID));
343 assert!(flags.has(NodeFlags::HAS_CHILDREN));
344 }
345
346 #[test]
347 fn new_element_sets_void_flag() {
348 let br = Node::new_element(Tag::Br, 0);
349 assert!(br.flags.has(NodeFlags::IS_VOID));
350
351 let div = Node::new_element(Tag::Div, 0);
352 assert!(!div.flags.has(NodeFlags::IS_VOID));
353 }
354
355 #[test]
356 fn new_text_sets_text_flag() {
357 let text = Node::new_text(1, 0, 5);
358 assert!(text.flags.has(NodeFlags::IS_TEXT));
359 assert_eq!(text.text_offset, 0);
360 assert_eq!(text.text_len, 5);
361 }
362}