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. Read-only.
26pub struct WalkCtx<'a> {
27  /// Ancestor count above this node. Top-level children = 0.
28  pub depth: usize,
29  /// Index among parent's children. 0-based.
30  pub index: usize,
31  /// `None` when visiting a top-level child of the document.
32  pub parent: Option<&'a Node>,
33}
34
35impl<'a> WalkCtx<'a> {
36  pub fn root() -> Self {
37    Self { depth: 0, index: 0, parent: None }
38  }
39  pub fn child(&self, parent: &'a Node, index: usize) -> Self {
40    Self { depth: self.depth + 1, index, parent: Some(parent) }
41  }
42}
43
44/// Pre-order DFS over `doc.children`. At every node, every sink's
45/// `enter` fires (in slice order); the walker then recurses into the
46/// node's children; finally every sink's `leave` fires (in reverse
47/// slice order, LIFO).
48///
49/// `Document` itself is not surfaced as a `Node::Document` event; the
50/// walker iterates `doc.children` directly. Sinks needing a document
51/// boundary subscribe to `Frontmatter` or to their first node.
52pub struct Walker<'a> {
53  doc: &'a Document,
54}
55
56impl<'a> Walker<'a> {
57  pub fn new(doc: &'a Document) -> Self {
58    Self { doc }
59  }
60
61  /// Drive the walk. `enter` fires slice-order, `leave` fires LIFO.
62  pub fn walk(self, sinks: &mut [&mut dyn NodeSink]) {
63    for (i, child) in self.doc.children.iter().enumerate() {
64      Self::walk_node(child, &WalkCtx { depth: 0, index: i, parent: None }, sinks);
65    }
66  }
67
68  fn walk_node(node: &'a Node, ctx: &WalkCtx<'a>, sinks: &mut [&mut dyn NodeSink]) {
69    for sink in sinks.iter_mut() {
70      sink.enter(node, ctx);
71    }
72    match node {
73      // Table rows/cells aren't `Node`s - handled inline.
74      Node::Table(t) => {
75        for row in &t.children {
76          for cell in &row.cells {
77            for (i, kid) in cell.children.iter().enumerate() {
78              Self::walk_node(kid, &ctx.child(node, i), sinks);
79            }
80          }
81        }
82      },
83      _ => {
84        for (i, kid) in Node::children_of(node).iter().enumerate() {
85          Self::walk_node(kid, &ctx.child(node, i), sinks);
86        }
87      },
88    }
89    for sink in sinks.iter_mut().rev() {
90      sink.leave(node, ctx);
91    }
92  }
93}