Skip to main content

dmc_codegen/
lib.rs

1//! User-facing walkthrough: ../../dmc-docs/dmc-codegen/
2//! Run `cargo doc --open -p dmc-codegen` for the inline rustdoc.
3
4//! Codegen layer: turn a parsed `Document` into renderable output.
5//!
6//! Two emitters live here:
7//! - [`HtmlEmitter`] - static HTML (SSR / SSG output).
8//! - [`MdxBodyEmitter`] - JS body for MDX runtime React rendering.
9//!
10//! Both implement [`NodeSink`]. A single [`Walker`] does one pre-order
11//! DFS, fanning each node to every active sink - no N tree traversals.
12mod escape;
13pub mod html;
14pub mod mdx;
15use dmc_parser::ast::{Document, Node};
16pub use html::{HtmlEmitter, RenderOptions, render_html, render_html_with};
17pub use mdx::{MdxBodyEmitter, render_mdx_body};
18
19/// Callback pair invoked by [`Walker`] at every node.
20pub trait NodeSink {
21  fn enter(&mut self, node: &Node, ctx: &WalkCtx);
22  fn leave(&mut self, _node: &Node, _ctx: &WalkCtx) {}
23}
24
25/// Position info handed to every sink callback.
26pub struct WalkCtx<'a> {
27  /// Ancestor count above this node. Top-level children = 0.
28  pub depth: usize,
29  pub index: usize,
30  /// `None` when visiting a top-level child of the document.
31  pub parent: Option<&'a Node>,
32}
33
34impl<'a> WalkCtx<'a> {
35  pub fn root() -> Self {
36    Self { depth: 0, index: 0, parent: None }
37  }
38  pub fn child(&self, parent: &'a Node, index: usize) -> Self {
39    Self { depth: self.depth + 1, index, parent: Some(parent) }
40  }
41}
42
43/// Pre-order DFS over `doc.children`. Sinks fire `enter` in slice order,
44/// `leave` in reverse (LIFO). `Document` is not surfaced as a node event;
45/// sinks needing a document boundary subscribe to `Frontmatter` or their
46/// first node.
47pub struct Walker<'a> {
48  doc: &'a Document,
49}
50
51impl<'a> Walker<'a> {
52  pub fn new(doc: &'a Document) -> Self {
53    Self { doc }
54  }
55
56  pub fn walk(self, sinks: &mut [&mut dyn NodeSink]) {
57    for (i, child) in self.doc.children.iter().enumerate() {
58      Self::walk_node(child, &WalkCtx { depth: 0, index: i, parent: None }, sinks);
59    }
60  }
61
62  fn walk_node(node: &'a Node, ctx: &WalkCtx<'a>, sinks: &mut [&mut dyn NodeSink]) {
63    for sink in sinks.iter_mut() {
64      sink.enter(node, ctx);
65    }
66    match node {
67      // Table rows/cells aren't `Node`s - handled inline.
68      Node::Table(t) => {
69        for row in &t.children {
70          for cell in &row.cells {
71            for (i, kid) in cell.children.iter().enumerate() {
72              Self::walk_node(kid, &ctx.child(node, i), sinks);
73            }
74          }
75        }
76      },
77      _ => {
78        for (i, kid) in Node::children_of(node).iter().enumerate() {
79          Self::walk_node(kid, &ctx.child(node, i), sinks);
80        }
81      },
82    }
83    for sink in sinks.iter_mut().rev() {
84      sink.leave(node, ctx);
85    }
86  }
87}