Rust in Everything
基于 pulldown-cmark 0.13.5

块解析

pulldown-cmark 分两遍解析。第一遍在 Parser::new_ext 里一次做完:从头到尾读一遍输入,认出所有的块(段落、标题、代码块、列表、引用块、表格……),建成一棵存在 Vec 里的树;块里的行内内容这时只扫一遍、标出可能是行内标记的位置,留给第二遍。这一部分讲第一遍,也就是块主线:例子文档的 21 行怎么变成一个 70 项的节点数组(含下标 0 的占位)。

代码分布(按 tag v0.13.5 统计):firstpass.rs 2849 行(其中最后约 270 行是只在 x86_64 开 simd 时编译的查表实现),tree.rs 298 行,行首和各种块标记的扫描器在 scanners.rs(1566 行),scan_containers 和 Item / ItemBody 的定义在 parse.rs。每页都对照 CommonMark 0.31.2 规范(仓库里的 third_party/CommonMark/spec.txt)的对应章节。

  1. 扁平数组上的树:Tree 只有 nodes、spine、cur 三个字段,节点用从 1 开始的 NonZeroUsize 下标相连,Node<Item> 实测 48 字节;第二遍用同一个 spine 当显式栈走树。
  2. 一行一行:parse_block 一次处理一个块,LineStart 按 4 列制表位扫行首,scan_containers 判断哪些容器还开着,惰性续行在段落循环里处理;0.13.5 修掉了深层列表加空行的平方复杂度。
  3. 叶子块:段落怎么在下一行变成 Setext 标题,ATX 标题、分隔线、两种代码块、七类 HTML 块、元数据块;0.13.5 与规范的两处出入。
  4. 容器块:引用块与 GFM 提示块,列表与列表项的紧凑和松散,任务列表,定义列表。
  5. 表格与脚注:表格要等第二行的分隔行才成立,对齐存在 Allocations;两种风格的脚注定义;链接引用定义在第一遍收进 refdefs。
flowchart TB
  new["Parser new_ext"] --> rfp["run_first_pass"]
  rfp --> pb["parse_block 循环"]
  pb --> sc["scan_containers"]
  sc --> cont["容器块"]
  sc --> leaf["叶子块"]
  leaf --> pl["parse_line 标占位"]
  cont --> tree["Tree of Item"]
  pl --> tree
  pb --> allocs["Allocations"]
  tree --> next["Parser next"]
  allocs --> next

第一遍产出的是树和 Allocations 两样东西:树里只有字节范围和小下标,链接定义、info string、表格对齐、标题属性这些变长数据在 Allocations 里(一次解析分配了什么)。第二遍怎么沿着树把占位节点解析成强调、链接,见按需解析;两遍连起来走一份文档,见一份文档。

💬 关联讨论

发起讨论
加载中...