首页 / 资讯中心 / 文章详情

mermaid-rs-renderer 源码剖析(一):Parser 与 IR——如何把 Mermaid 语法解析成图

mermaid-rs-renderer 源码剖析(一):Parser 与 IR——如何把 Mermaid 语法解析成图 ★ FEATURED ARTICLE
【免费下载链接】mermaid-rs-rendererA fast native Rust Mermaid diagram renderer. No browser required. 500-1000x faster than mermaid-cli.项目地址https://gitcode.com/gh_mirrors/me/mermaid-rs-renderer点击查看免费下载mermaid-rs-renderer简称mmdr是一个用纯 Rust 写的Mermaid 图表渲染器无需浏览器比 mermaid-cli 快 100–1400 倍。它把一段.mmd文本解析成图表再渲染为 SVG/PNG。本篇从源码角度拆解它最核心的两步——Parser解析器与IR中间表示讲清楚「一段 Mermaid 语法是如何变成一个可编程的图的」。渲染管线全景一行 Mermaid 到 SVG 的四段路在深入之前先建立全局地图。mmdr把渲染拆成了四个清晰分离的阶段.mmd 文本 → parser.rs → ir.rs (Graph) → layout.rs → render.rs → SVG → (resvg) → PNG每个阶段只做一件事阶段职责关键文件Parse解析识别图表类型、逐行解析语法src/parser.rsIR中间表示把语法存成结构化数据Graphsrc/ir.rsLayout布局算出每个节点/连线的坐标src/layout/Render渲染把坐标画成 SVG 字符串src/render.rs本篇聚焦前两步。整体架构决策记录在 docs/architecture.md。先看mmdr的解析渲染最终能产出什么效果左为mmdr右为官方 mermaid-cli对比来自 benches/fixtures第一步Parser 如何认出 23 种图表一切入口是 parse_mermaid()pub fn parse_mermaid(input: str) - ResultParseOutput { validate_init_directives(input)?; let Some(kind) detect_diagram_kind(input) else { bail!(unknown or missing Mermaid diagram header); }; match kind { /* 分派到 23 个解析函数 */ } }它做了两件关键的事识别图表类型—— detect_diagram_kind() 扫描每一行看开头是不是flowchart、sequenceDiagram、erDiagram……等 23 个关键词返回一个 DiagramKind 枚举。分派给专门的解析函数—— 每种图表一个函数parse_flowchart、parse_sequence_diagram、parse_er_diagram……互不干扰。这个「关键词开头 分派」的设计非常易读想加新图表只需在detect_diagram_kind里加一个匹配再写一个parse_xxx即可。预处理先打扫输入再解析真正逐行解析前preprocess_input() 会先把原始文本打扫干净跳过 frontmatter以---包裹的 Markdown 元数据区域直接忽略提取%%{init: ...}%%指令这是 Mermaid 的主题/配置开关比如换主题色解析成 JSON 单独保存不参与图形本身去掉%%注释行尾注释被 strip_trailing_comment() 剥离去空行空行直接丢弃。这样一来交给解析器的每一行都是干净的、有效的语句。预处理把配置和图形分开是 IR 设计能保持简洁的前提。Flowchart 逐行解析一个手写状态机Flowchart流程图是最常用也最典型的图表我们以它为例。parse_flowchart() 是一个逐行扫描的状态机用一张 benches/typical/flowchart.mmd 来对照flowchart TD A[Start] -- B{Decision} B --|Yes| C[Process 1] B --|No| D[Process 2]每一行都会按顺序尝试匹配HEADER_RE→ 识别flowchart TD记下方向TDend→ 关闭当前子图subgraphSUBGRAPH_RE→ 遇到subgraph压入子图栈direction LR→ 设置子图内部方向classDef/class/style/linkStyle→ 样式指令直接落到 IR连边→A -- B这类带箭头的行孤节点→ 只声明节点不带边的行。注意它用了一个subgraph_stack子图栈来跟踪当前节点属于哪个子图从而支持嵌套subgraph。这是典型的手写递归下降解析不依赖任何解析器生成器逻辑一目了然。节点形状方括号里藏着什么一个节点 token 如B{Decision}{和}决定了它的形状。parse_node_token() 负责拆解出id、label、shape三要素let (id, label, shape) split_id_label(trimmed); // B{Decision} → (B, Decision, Diamond)split_id_label() 按括号类型映射到 NodeShape 枚举这套映射几乎与 Mermaid 官方一一对应语法形状语法形状[text]矩形((text))双圆(text)圆角[[text]]子程序{text}菱形[(text)]圆柱{{text}}六边形text]不对称解析结果里:::class这类内联样式会被 split_inline_classes() 单独剥离出来。连边解析拆链、拆标签Flowchart 里最狡猾的是连边。一行A -- B -- C其实是一条边链chainsplit_edge_chain() 会先把它拆成A -- B和B -- C两条。再看带标签的B --|Yes| Cparse_edge_line() 用一组正则PIPE_LABEL_RE等把「左节点」「箭头样式」「标签」「右节点」四部分切开再交给 parse_edge_meta() 判断箭头方向、虚线/粗线等细节。解析完连边变成一个 Edge 结构from、to、label、directed、style……每个字段都为后续布局/渲染准备好。第二步IR——Graph 结构体解析的终点是一个统一的 Graph 结构体。它是整个项目的心脏把 23 种图表拍平进同一个类型pub struct Graph { pub kind: DiagramKind, pub direction: Direction, pub nodes: BTreeMapString, Node, pub node_order: HashMapString, usize, pub edges: VecEdge, pub subgraphs: VecSubgraph, pub sequence_participants: VecString, pub pie_slices: VecPieSlice, pub gantt_tasks: VecGanttTask, /* ...每种图表专属的字段 */ }设计上有两个巧思值得注意BTreeMap存节点用有序映射而非Vec保证节点按 id 稳定排序这是输出确定性deterministic output的关键——同样输入永远得到同样坐标方便做 diff。node_order记录写入顺序虽然BTreeMap有序但布局时还需要作者书写顺序所以额外用HashMap记下每个节点首次出现的序号。节点通过 ensure_node() 创建如果节点已存在就更新标签/形状不存在才新建——这完美匹配 Mermaid「在连边里提到节点就自动创建」的语义。下面这张 C4 架构图benches/fixtures/c4_medium.mmd就来自这样一个Graph人物、系统、边界框、带标签的连线全部是nodesedgessubgraphs的组合一个 Graph 承载 23 种图表你可能会问为什么流程图、序列图、饼图、Gantt……全塞进同一个Graph答案是统一管线。每种图表解析后把自己特有的数据填进Graph的对应字段流程图填nodes/edges序列图填sequence_participantssequence_frames饼图填pie_slices然后走同一条layout → render。布局层只需按kind分派而不用面对 23 套完全不同的类型系统。比如序列图 benches/typical/sequence.mmd它的参与者、消息、alt分支框最终分别落在sequence_participants、edges、sequence_frames三个字段里这种宽结构体 枚举分派的取舍是用一定字段冗余换取了管线复用与代码可维护性。从 Parser 到 Layout 的交接解析完成后parse_mermaid() 返回一个 ParseOutput包含两样东西pub struct ParseOutput { pub graph: Graph, // 结构化的图 pub init_config: OptionValue, // %%{init}%% 提取的配置 }graph交给layout阶段算坐标init_config交给render阶段决定主题在 resolve_options() 里init 指令会覆盖默认主题与 mermaid.js 语义一致。如果输入不合法mmdr还有一套带行列号的类型化错误 ParseError如第 N 行未声明的参与者未闭合的 subgraph让编辑器、CMS 甚至 LLM 修复循环都能给出可操作的诊断。小结本篇带你看完了mmdr的上半场Parsersrc/parser.rs——逐行、手写、按图表类型分派把 Mermaid 语法拆成节点、连边、子图和各类指令IRsrc/ir.rs——用一个统一的Graph结构体承载全部 23 种图表用有序结构保证输出确定性。这两步把文本变成了可编程的数据。下一篇《源码剖析二》我们继续深入Layout 布局引擎——节点如何分层、连线如何避让、preferredAspectRatio又是如何重排几何的。更多示例与基准见 benches/ 目录逐图渲染对比报告在 docs/conformance-report/。赞分享【免费下载链接】mermaid-rs-rendererA fast native Rust Mermaid diagram renderer. No browser required. 500-1000x faster than mermaid-cli.项目地址https://gitcode.com/gh_mirrors/me/mermaid-rs-renderer点击查看免费下载相关推荐Mermaid Venn 图venn-beta语法详解与源码实现剖析Mermaid Venn 图venn beta语法详解与源码实现剖析 本文围绕 Mermaid 官方语法文档 docs/syntax/venn.md htt图表库前端数据可视化如何用 mermaid-rs-renderer 批量渲染 Markdown 中的 Mermaid 图表CLI 实战指南如何用 mermaid rs renderer 批量渲染 Markdown 中的 Mermaid 图表CLI 实战指南 mermaid rs rendererMermaid 图表一次画 23 种mermaid-rs-renderer 全类型支持完整清单与示例Mermaid 图表一次画 23 种mermaid rs renderer 全类型支持完整清单与示例 mermaid rs renderermmdr是一个创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站