1use indexmap::IndexMap;
26
27use crate::ast::{AttributeLocation, Location, ValidationError, Value};
28use crate::grammar::Attribute;
29
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
40#[non_exhaustive]
41pub enum NodeType {
42 Blockquote,
44 Code,
46 Comment,
48 Document,
50 Em,
52 Error,
55 Fence,
57 Hardbreak,
59 Heading,
61 Hr,
63 Image,
65 Inline,
74 Item,
76 Link,
78 List,
80 #[default]
83 Node,
84 Paragraph,
86 S,
88 Softbreak,
90 Strong,
92 Table,
94 Tag,
96 Tbody,
98 Td,
100 Text,
103 Th,
105 Thead,
107 Tr,
109}
110
111impl NodeType {
112 #[must_use]
114 pub const fn as_str(self) -> &'static str {
115 match self {
116 NodeType::Blockquote => "blockquote",
117 NodeType::Code => "code",
118 NodeType::Comment => "comment",
119 NodeType::Document => "document",
120 NodeType::Em => "em",
121 NodeType::Error => "error",
122 NodeType::Fence => "fence",
123 NodeType::Hardbreak => "hardbreak",
124 NodeType::Heading => "heading",
125 NodeType::Hr => "hr",
126 NodeType::Image => "image",
127 NodeType::Inline => "inline",
128 NodeType::Item => "item",
129 NodeType::Link => "link",
130 NodeType::List => "list",
131 NodeType::Node => "node",
132 NodeType::Paragraph => "paragraph",
133 NodeType::S => "s",
134 NodeType::Softbreak => "softbreak",
135 NodeType::Strong => "strong",
136 NodeType::Table => "table",
137 NodeType::Tag => "tag",
138 NodeType::Tbody => "tbody",
139 NodeType::Td => "td",
140 NodeType::Text => "text",
141 NodeType::Th => "th",
142 NodeType::Thead => "thead",
143 NodeType::Tr => "tr",
144 }
145 }
146
147 pub const ALL: [NodeType; 28] = [
158 NodeType::Blockquote,
159 NodeType::Code,
160 NodeType::Comment,
161 NodeType::Document,
162 NodeType::Em,
163 NodeType::Error,
164 NodeType::Fence,
165 NodeType::Hardbreak,
166 NodeType::Heading,
167 NodeType::Hr,
168 NodeType::Image,
169 NodeType::Inline,
170 NodeType::Item,
171 NodeType::Link,
172 NodeType::List,
173 NodeType::Node,
174 NodeType::Paragraph,
175 NodeType::S,
176 NodeType::Softbreak,
177 NodeType::Strong,
178 NodeType::Table,
179 NodeType::Tag,
180 NodeType::Tbody,
181 NodeType::Td,
182 NodeType::Text,
183 NodeType::Th,
184 NodeType::Thead,
185 NodeType::Tr,
186 ];
187
188 #[must_use]
197 pub fn from_name(name: &str) -> Option<NodeType> {
198 Some(match name {
201 "blockquote" => NodeType::Blockquote,
202 "code" => NodeType::Code,
203 "comment" => NodeType::Comment,
204 "document" => NodeType::Document,
205 "em" => NodeType::Em,
206 "error" => NodeType::Error,
207 "fence" => NodeType::Fence,
208 "hardbreak" => NodeType::Hardbreak,
209 "heading" => NodeType::Heading,
210 "hr" => NodeType::Hr,
211 "image" => NodeType::Image,
212 "inline" => NodeType::Inline,
213 "item" => NodeType::Item,
214 "link" => NodeType::Link,
215 "list" => NodeType::List,
216 "node" => NodeType::Node,
217 "paragraph" => NodeType::Paragraph,
218 "s" => NodeType::S,
219 "softbreak" => NodeType::Softbreak,
220 "strong" => NodeType::Strong,
221 "table" => NodeType::Table,
222 "tag" => NodeType::Tag,
223 "tbody" => NodeType::Tbody,
224 "td" => NodeType::Td,
225 "text" => NodeType::Text,
226 "th" => NodeType::Th,
227 "thead" => NodeType::Thead,
228 "tr" => NodeType::Tr,
229 _ => return None,
230 })
231 }
232}
233
234impl std::fmt::Display for NodeType {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 f.write_str(self.as_str())
237 }
238}
239
240#[derive(Default)]
247pub struct Node<'a> {
248 pub node_type: NodeType,
250 pub tag: Option<String>,
252 pub attributes: IndexMap<String, Value>,
259 pub children: Vec<Node<'a>>,
261 pub slots: IndexMap<String, Node<'a>>,
267 pub errors: Vec<ValidationError<'a>>,
272 pub lines: Vec<usize>,
276 pub annotations: Vec<Attribute>,
282 pub annotation_locations: Vec<AttributeLocation<'a>>,
295 pub inline: bool,
297 pub location: Option<Location<'a>>,
299}
300
301impl Clone for Node<'_> {
313 fn clone(&self) -> Self {
314 enum Step<'s, 'a> {
315 Open(&'s Node<'a>),
316 Close(&'s Node<'a>),
317 }
318
319 let mut plan = vec![Step::Open(self)];
320 let mut done: Vec<Node<'_>> = Vec::new();
321
322 while let Some(step) = plan.pop() {
323 match step {
324 Step::Open(node) => {
325 plan.push(Step::Close(node));
326 for child in node.children.iter().rev() {
329 plan.push(Step::Open(child));
330 }
331 for (_, slot) in node.slots.iter().rev() {
332 plan.push(Step::Open(slot));
333 }
334 }
335 Step::Close(node) => {
336 let total = node.slots.len() + node.children.len();
337 let start = done.len().saturating_sub(total);
338 let mut finished = done.split_off(start).into_iter();
339
340 let slots: IndexMap<String, Node<'_>> = node
341 .slots
342 .keys()
343 .cloned()
344 .zip(finished.by_ref().take(node.slots.len()))
345 .collect();
346 let children: Vec<Node<'_>> = finished.collect();
347
348 done.push(Node {
349 node_type: node.node_type,
350 tag: node.tag.clone(),
351 attributes: node.attributes.clone(),
352 children,
353 slots,
354 errors: node.errors.clone(),
355 lines: node.lines.clone(),
356 annotations: node.annotations.clone(),
357 annotation_locations: node.annotation_locations.clone(),
358 inline: node.inline,
359 location: node.location,
360 });
361 }
362 }
363 }
364
365 done.pop().unwrap_or_default()
366 }
367}
368
369impl PartialEq for Node<'_> {
370 fn eq(&self, other: &Self) -> bool {
371 let mut work: Vec<(&Node<'_>, &Node<'_>)> = vec![(self, other)];
372 while let Some((left, right)) = work.pop() {
373 if left.node_type != right.node_type
375 || left.tag != right.tag
376 || left.attributes != right.attributes
377 || left.errors != right.errors
378 || left.lines != right.lines
379 || left.annotations != right.annotations
380 || left.annotation_locations != right.annotation_locations
381 || left.inline != right.inline
382 || left.location != right.location
383 || left.children.len() != right.children.len()
384 || left.slots.len() != right.slots.len()
385 {
386 return false;
387 }
388 work.extend(left.children.iter().zip(right.children.iter()));
389 for (key, slot) in &left.slots {
391 match right.slots.get(key) {
392 Some(other_slot) => work.push((slot, other_slot)),
393 None => return false,
394 }
395 }
396 }
397 true
398 }
399}
400
401impl std::fmt::Debug for Node<'_> {
402 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403 let alternate = f.alternate();
404 let mut stack: Vec<NodeTok<'_, '_>> = vec![NodeTok::Node(self, 0)];
405
406 while let Some(token) = stack.pop() {
407 match token {
408 NodeTok::Text(text) => f.write_str(text)?,
409 NodeTok::Owned(text) => f.write_str(&text)?,
410 NodeTok::Line(depth) => {
411 f.write_str("\n")?;
412 for _ in 0..depth {
413 f.write_str(" ")?;
414 }
415 }
416 NodeTok::Node(node, depth) => expand_node(f, &mut stack, node, depth, alternate)?,
417 }
418 }
419 Ok(())
420 }
421}
422
423enum NodeTok<'n, 'a> {
425 Node(&'n Node<'a>, usize),
426 Text(&'static str),
427 Owned(String),
428 Line(usize),
429}
430
431fn indent_block(body: &str, depth: usize) -> String {
434 let pad = " ".repeat(depth);
435 body.replace('\n', &format!("\n{pad}"))
436}
437
438fn expand_node<'n, 'a>(
443 f: &mut std::fmt::Formatter<'_>,
444 stack: &mut Vec<NodeTok<'n, 'a>>,
445 node: &'n Node<'a>,
446 depth: usize,
447 alternate: bool,
448) -> std::fmt::Result {
449 fn flat(value: &dyn std::fmt::Debug, depth: usize, alternate: bool) -> String {
451 if alternate {
452 indent_block(&format!("{value:#?}"), depth)
453 } else {
454 format!("{value:?}")
455 }
456 }
457
458 let mut queued: Vec<NodeTok<'n, 'a>> = Vec::new();
459
460 if alternate {
461 let inner = depth + 1;
462 f.write_str("Node {")?;
463 for (name, rendered) in [
464 ("node_type", flat(&node.node_type, inner, true)),
465 ("tag", flat(&node.tag, inner, true)),
466 ("attributes", flat(&node.attributes, inner, true)),
467 ] {
468 queued.push(NodeTok::Line(inner));
469 queued.push(NodeTok::Owned(format!("{name}: {rendered},")));
470 }
471
472 queued.push(NodeTok::Line(inner));
473 if node.children.is_empty() {
474 queued.push(NodeTok::Text("children: [],"));
475 } else {
476 queued.push(NodeTok::Text("children: ["));
477 for child in &node.children {
478 queued.push(NodeTok::Line(inner + 1));
479 queued.push(NodeTok::Node(child, inner + 1));
480 queued.push(NodeTok::Text(","));
481 }
482 queued.push(NodeTok::Line(inner));
483 queued.push(NodeTok::Text("],"));
484 }
485
486 queued.push(NodeTok::Line(inner));
487 if node.slots.is_empty() {
488 queued.push(NodeTok::Text("slots: {},"));
489 } else {
490 queued.push(NodeTok::Text("slots: {"));
491 for (key, slot) in &node.slots {
492 queued.push(NodeTok::Line(inner + 1));
493 queued.push(NodeTok::Owned(format!("{key:?}: ")));
494 queued.push(NodeTok::Node(slot, inner + 1));
495 queued.push(NodeTok::Text(","));
496 }
497 queued.push(NodeTok::Line(inner));
498 queued.push(NodeTok::Text("},"));
499 }
500
501 for (name, rendered) in [
502 ("errors", flat(&node.errors, inner, true)),
503 ("lines", flat(&node.lines, inner, true)),
504 ("annotations", flat(&node.annotations, inner, true)),
505 (
506 "annotation_locations",
507 flat(&node.annotation_locations, inner, true),
508 ),
509 ("inline", flat(&node.inline, inner, true)),
510 ("location", flat(&node.location, inner, true)),
511 ] {
512 queued.push(NodeTok::Line(inner));
513 queued.push(NodeTok::Owned(format!("{name}: {rendered},")));
514 }
515 queued.push(NodeTok::Line(depth));
516 queued.push(NodeTok::Text("}"));
517 } else {
518 write!(
519 f,
520 "Node {{ node_type: {}, tag: {}, attributes: {}, children: [",
521 flat(&node.node_type, depth, false),
522 flat(&node.tag, depth, false),
523 flat(&node.attributes, depth, false),
524 )?;
525 for (index, child) in node.children.iter().enumerate() {
526 if index > 0 {
527 queued.push(NodeTok::Text(", "));
528 }
529 queued.push(NodeTok::Node(child, depth));
530 }
531 queued.push(NodeTok::Text("], slots: {"));
532 for (index, (key, slot)) in node.slots.iter().enumerate() {
533 if index > 0 {
534 queued.push(NodeTok::Text(", "));
535 }
536 queued.push(NodeTok::Owned(format!("{key:?}: ")));
537 queued.push(NodeTok::Node(slot, depth));
538 }
539 queued.push(NodeTok::Owned(format!(
540 "}}, errors: {}, lines: {}, annotations: {}, annotation_locations: {}, \
541 inline: {}, location: {} }}",
542 flat(&node.errors, depth, false),
543 flat(&node.lines, depth, false),
544 flat(&node.annotations, depth, false),
545 flat(&node.annotation_locations, depth, false),
546 flat(&node.inline, depth, false),
547 flat(&node.location, depth, false),
548 )));
549 }
550
551 stack.extend(queued.into_iter().rev());
552 Ok(())
553}
554
555impl<'a> Node<'a> {
556 #[must_use]
564 pub fn new(node_type: NodeType) -> Node<'a> {
565 Node {
566 node_type,
567 tag: None,
568 attributes: IndexMap::new(),
569 children: Vec::new(),
570 slots: IndexMap::new(),
571 errors: Vec::new(),
572 lines: Vec::new(),
573 annotations: Vec::new(),
574 annotation_locations: Vec::new(),
575 inline: false,
576 location: None,
577 }
578 }
579
580 #[must_use]
585 pub fn with(
586 node_type: NodeType,
587 attributes: IndexMap<String, Value>,
588 children: Vec<Node<'a>>,
589 tag: Option<String>,
590 ) -> Node<'a> {
591 Node {
592 node_type,
593 tag,
594 attributes,
595 children,
596 slots: IndexMap::new(),
597 errors: Vec::new(),
598 lines: Vec::new(),
599 annotations: Vec::new(),
600 annotation_locations: Vec::new(),
601 inline: false,
602 location: None,
603 }
604 }
605
606 pub fn push(&mut self, node: Node<'a>) {
608 self.children.push(node);
609 }
610
611 pub fn set(&mut self, name: impl Into<String>, value: Value) {
617 self.attributes.insert(name.into(), value);
618 }
619
620 #[must_use]
622 pub fn get(&self, name: &str) -> Option<&Value> {
623 self.attributes.get(name)
624 }
625
626 #[must_use]
631 pub fn name(&self) -> &str {
632 self.tag
633 .as_deref()
634 .unwrap_or_else(|| self.node_type.as_str())
635 }
636
637 #[must_use]
644 pub fn walk(&self) -> Walk<'_, 'a> {
645 Walk {
646 stack: self.descendants_in_order(),
647 }
648 }
649
650 fn descendants_in_order(&self) -> Vec<&Node<'a>> {
654 let mut out: Vec<&Node<'a>> = self.slots.values().chain(self.children.iter()).collect();
655 out.reverse();
656 out
657 }
658}
659
660impl Drop for Node<'_> {
674 fn drop(&mut self) {
675 let mut pending: Vec<Node<'_>> = std::mem::take(&mut self.children);
676 pending.extend(self.slots.drain(..).map(|(_, node)| node));
677 while let Some(mut node) = pending.pop() {
678 pending.append(&mut node.children);
679 pending.extend(node.slots.drain(..).map(|(_, child)| child));
680 }
682 }
683}
684
685pub struct Walk<'n, 'a> {
691 stack: Vec<&'n Node<'a>>,
692}
693
694impl<'n, 'a> Iterator for Walk<'n, 'a> {
695 type Item = &'n Node<'a>;
696
697 fn next(&mut self) -> Option<&'n Node<'a>> {
698 let node = self.stack.pop()?;
699 self.stack.extend(node.descendants_in_order());
700 Some(node)
701 }
702}
703
704#[cfg(test)]
707mod debug_parity {
708 use super::*;
709
710 mod mirror {
711 #![allow(dead_code, clippy::struct_field_names)]
714
715 use super::{Attribute, AttributeLocation, Location, NodeType, ValidationError, Value};
716 use indexmap::IndexMap;
717
718 #[derive(Debug)]
719 pub struct Node<'a> {
720 pub node_type: NodeType,
721 pub tag: Option<String>,
722 pub attributes: IndexMap<String, Value>,
723 pub children: Vec<Node<'a>>,
724 pub slots: IndexMap<String, Node<'a>>,
725 pub errors: Vec<ValidationError<'a>>,
726 pub lines: Vec<usize>,
727 pub annotations: Vec<Attribute>,
728 pub annotation_locations: Vec<AttributeLocation<'a>>,
729 pub inline: bool,
730 pub location: Option<Location<'a>>,
731 }
732 }
733
734 fn to_mirror<'a>(node: &Node<'a>) -> mirror::Node<'a> {
735 mirror::Node {
736 node_type: node.node_type,
737 tag: node.tag.clone(),
738 attributes: node.attributes.clone(),
739 children: node.children.iter().map(to_mirror).collect(),
740 slots: node
741 .slots
742 .iter()
743 .map(|(key, slot)| (key.clone(), to_mirror(slot)))
744 .collect(),
745 errors: node.errors.clone(),
746 lines: node.lines.clone(),
747 annotations: node.annotations.clone(),
748 annotation_locations: node.annotation_locations.clone(),
749 inline: node.inline,
750 location: node.location,
751 }
752 }
753
754 fn assert_parity(node: &Node<'_>) {
755 let reference = to_mirror(node);
756 assert_eq!(format!("{node:?}"), format!("{reference:?}"), "plain Debug");
757 assert_eq!(
758 format!("{node:#?}"),
759 format!("{reference:#?}"),
760 "alternate Debug"
761 );
762 }
763
764 #[test]
765 fn every_node_shape_formats_as_the_derive_would() {
766 let mut bare = Node::new(NodeType::Paragraph);
767 bare.lines = vec![1, 2];
768
769 let mut attributed = Node::new(NodeType::Tag);
770 attributed.tag = Some("callout".to_owned());
771 attributed.set("level", Value::Number(2.0));
772 attributed.set("title", Value::String("hi".to_owned()));
773 attributed.inline = true;
774
775 let nested = Node::with(
776 NodeType::Document,
777 IndexMap::new(),
778 vec![Node::with(
779 NodeType::Paragraph,
780 IndexMap::new(),
781 vec![Node::new(NodeType::Text)],
782 None,
783 )],
784 None,
785 );
786
787 let mut slotted = Node::new(NodeType::Tag);
788 slotted.tag = Some("card".to_owned());
789 slotted
790 .slots
791 .insert("header".to_owned(), Node::new(NodeType::Paragraph));
792
793 let mut deep_attribute = Node::new(NodeType::Tag);
796 deep_attribute.set(
797 "data",
798 Value::Array(vec![Value::Hash(
799 [("k".to_owned(), Value::Null)].into_iter().collect(),
800 )]),
801 );
802
803 for shape in &[bare, attributed, nested, slotted, deep_attribute] {
804 assert_parity(shape);
805 }
806 }
807
808 #[test]
809 fn a_deep_node_survives_all_three_traversals() {
810 let mut node = Node::new(NodeType::Paragraph);
813 for _ in 0..100_000 {
814 node = Node::with(NodeType::Tag, IndexMap::new(), vec![node], Some("a".into()));
815 }
816 let copy = node.clone();
817 assert!(copy == node, "an iterative clone must equal its source");
818 assert!(format!("{node:?}").starts_with("Node { node_type: Tag"));
819 }
820
821 #[test]
822 fn a_node_deep_through_slots_survives_all_three() {
823 let mut node = Node::new(NodeType::Paragraph);
824 for _ in 0..100_000 {
825 let mut outer = Node::new(NodeType::Tag);
826 outer.slots.insert("s".to_owned(), node);
827 node = outer;
828 }
829 let copy = node.clone();
830 assert_eq!(copy, node);
831 }
832
833 #[test]
834 fn cloning_preserves_child_and_slot_order() {
835 let mut node = Node::with(
836 NodeType::Document,
837 IndexMap::new(),
838 vec![Node::new(NodeType::Heading), Node::new(NodeType::Paragraph)],
839 None,
840 );
841 node.slots.insert("z".to_owned(), Node::new(NodeType::Text));
842 node.slots
843 .insert("a".to_owned(), Node::new(NodeType::Fence));
844
845 let copy = node.clone();
846 assert_eq!(copy.children.len(), 2);
847 assert_eq!(copy.children[0].node_type, NodeType::Heading);
848 assert_eq!(copy.children[1].node_type, NodeType::Paragraph);
849 assert_eq!(copy.slots.keys().collect::<Vec<_>>(), ["z", "a"]);
850 assert_eq!(copy.slots["a"].node_type, NodeType::Fence);
851 assert_eq!(copy, node);
852 }
853}
854
855#[cfg(test)]
856mod tests {
857 use super::*;
858
859 fn text(content: &str) -> Node<'static> {
860 let mut node = Node::new(NodeType::Text);
861 node.set("content", Value::String(content.to_string()));
862 node
863 }
864
865 fn block(node_type: NodeType, children: Vec<Node<'static>>) -> Node<'static> {
866 Node::with(node_type, IndexMap::new(), children, None)
867 }
868
869 #[test]
871 fn walking_a_simple_document_visits_every_descendant() {
872 let example = block(
873 NodeType::Document,
874 vec![
875 block(
876 NodeType::Heading,
877 vec![block(NodeType::Inline, vec![text("This is a heading")])],
878 ),
879 block(
880 NodeType::Paragraph,
881 vec![block(NodeType::Inline, vec![text("This is a paragraph")])],
882 ),
883 ],
884 );
885
886 assert_eq!(example.walk().count(), 6);
887 }
888
889 #[test]
890 fn walking_visits_slots_before_children() {
891 let mut tag = Node::with(
895 NodeType::Tag,
896 IndexMap::new(),
897 Vec::new(),
898 Some("example".into()),
899 );
900 tag.slots.insert(
901 "foo".to_string(),
902 block(
903 NodeType::Paragraph,
904 vec![block(NodeType::Inline, vec![text("baz")])],
905 ),
906 );
907 tag.push(block(
908 NodeType::Heading,
909 vec![block(NodeType::Inline, vec![text("bar")])],
910 ));
911 let document = block(NodeType::Document, vec![tag]);
912
913 let visited: Vec<String> = document
914 .walk()
915 .map(|node| node.name().to_string())
916 .collect();
917 assert_eq!(
918 visited,
919 [
920 "example",
921 "paragraph",
922 "inline",
923 "text",
924 "heading",
925 "inline",
926 "text"
927 ]
928 );
929 }
930
931 #[test]
932 fn walking_is_iterative_and_survives_deep_nesting() {
933 let mut node = Node::new(NodeType::Document);
936 for _ in 0..50_000 {
937 node = block(NodeType::Tag, vec![node]);
938 }
939 assert_eq!(node.walk().count(), 50_000);
940 }
941
942 #[test]
943 fn attribute_order_is_authored_order() {
944 let mut node = Node::new(NodeType::Tag);
945 node.set("z", Value::Number(1.0));
946 node.set("a", Value::Number(2.0));
947 node.set("z", Value::Number(3.0));
948 let keys: Vec<&str> = node.attributes.keys().map(String::as_str).collect();
949 assert_eq!(keys, ["z", "a"]);
950 assert_eq!(node.get("z"), Some(&Value::Number(3.0)));
951 }
952
953 #[test]
954 fn a_node_names_itself_by_tag_then_type() {
955 assert_eq!(Node::new(NodeType::Paragraph).name(), "paragraph");
956 let mut tagged = Node::new(NodeType::Tag);
957 tagged.tag = Some("callout".to_string());
958 assert_eq!(tagged.name(), "callout");
959 }
960
961 #[test]
962 fn node_types_spell_themselves_as_upstream_does() {
963 assert_eq!(NodeType::Fence.as_str(), "fence");
964 assert_eq!(NodeType::Hardbreak.to_string(), "hardbreak");
965 assert_eq!(NodeType::default(), NodeType::Node);
966 }
967}
968
969#[cfg(test)]
970mod node_type_list {
971 use super::NodeType;
972
973 #[test]
974 fn all_round_trips_through_its_names_and_repeats_none() {
975 let mut seen = std::collections::HashSet::new();
976 for node_type in NodeType::ALL {
977 assert_eq!(NodeType::from_name(node_type.as_str()), Some(node_type));
978 assert!(seen.insert(node_type), "{node_type} is listed twice");
979 }
980 }
981}