一路反射到底

一路反射到底

💡 原文英文,约2400词,阅读约需9分钟。
📝

内容提要

本文介绍MrDocs文档工具的重大更新:用自研MrDocs.Describe替代Boost.Describe,实现元数据通用比较与序列化;新增Lua/JavaScript脚本扩展、数据驱动生成器及宏支持;修复模板特化、文档注释等多项缺陷;并维护Boost.StaticString库。

🔎

延伸解读

元数据驱动:从手写样板到通用代码

MrDocs 用自研的 MrDocs.Describe 替代 Boost.Describe,将元数据模型中的类型统一描述,从而把比较、序列化、模板绑定等操作从“每类型手写”变为“一次编写、通用复用”。文章指出,元数据树中约 130 个描述类型,其中 111 个在元数据模型中,手写方式难以扩展。这一改动不仅消除了大量重复代码,还使得新增字段时无需担心遗漏比较或序列化,因为通用实现会自动覆盖。

脚本扩展的三种层级:从助手到完整生成器

MrDocs 提供了三种无需编译即可扩展的方式:脚本(Lua/JavaScript)注册变换或生成器、数据驱动的生成器(通过 YAML 清单和模板目录)、以及脚本驱动的生成器(完全控制输出循环)。这些层级设计使得用户可以根据需求选择灵活度:从简单的模板继承(如 Markdown 继承 HTML)到完全自定义的聚合输出(如全量 JSON 或搜索索引)。所有扩展均通过统一的上下文对象访问语料库,且脚本绑定由元数据自动生成,避免手工维护。

宏支持:独立于 C++ 符号的提取逻辑

宏与 C++ 符号在命名、作用域和提取行为上差异显著,因此 MrDocs 为宏单独设计了过滤选项(include-macros/exclude-macros)和提取开关(extract-all-macros,默认关闭)。宏没有成员或作用域,所以实现定义和 see-below 模式不适用。此外,对于未在构建中定义的特性测试宏,可通过 __MRDOCS__ 宏提供仅影响 MrDocs 的定义。这一设计避免了将宏错误地当作普通符号处理。

LLVM 升级的代价与收益

为了获取 Clang 对宏文档注释的支持,MrDocs 将 LLVM 固定版本前移了约六个月,一次性支付了 API 变更成本(如命名空间移动、函数重命名等)。文章强调,工具链升级在不需要新特性时最轻松,但为了宏注释功能,这次升级是必要的。升级后,宏的文档注释直接从 Clang 获取,替代了原先的源码文本扫描,简化了代码。这也提醒读者,依赖上游工具链时需权衡升级的即时成本与长期收益。

Q&A

MrDocs.Describe 是什么?它解决了什么问题?

MrDocs.Describe 是 MrDocs 自研的反射库,用于替代 Boost.Describe。它解决了为每个元数据结构手写比较、序列化和模板绑定代码的问题,通过统一的元数据描述,使得这些操作可以泛型化,减少了大量重复代码,并避免了因新增字段而遗漏更新相关函数的问题。

MrDocs 如何实现元数据类型的通用比较?

MrDocs 通过一个通用的 operator<=>() 和 operator==() 实现元数据类型的比较,定义在 Support/Reflection/CompareReflectedType.hpp 中。它先按描述顺序比较基类,再比较成员,并在发现不等时短路。这个通用实现替代了之前每个类型手写的比较重载。

MrDocs 支持哪些脚本语言进行扩展?如何注册扩展?

MrDocs 支持 Lua 和 JavaScript 作为扩展语言。扩展文件放在 addon 的 extensions/ 目录下,通过运行脚本的顶层代码注册:使用 mrdocs.register_transform(id, fn) 注册语料库变换,使用 mrdocs.register_generator(id, fn) 注册输出生成器。注册的函数在加载时不会立即执行,而是由宿主收集后调用。

数据驱动的生成器是如何工作的?

数据驱动的生成器通过 addon 的 generator/ 目录下的子目录定义,每个子目录包含一个 mrdocs-generator.yml 清单和模板。清单包含转义表和 extends 键,用于继承其他生成器的模板。生成器的 id、文件扩展名和显示名称由目录名决定。内置的 AdocGenerator 和 HTMLGenerator 现在只有约四十行代码,基于共享的 HandlebarsGenerator。

脚本驱动的生成器有什么优势?

脚本驱动的生成器允许脚本完全控制输出循环,可以生成跨所有符号的单一文件,如整个语料库的 JSON 转储或搜索索引。它通过 dom::Function 自拥有脚本 VM,因此同一实现可以驱动 Lua 和 JavaScript。文件写入 API 限制路径必须在输出目录内,并支持追加模式,便于流式写入大文件。

MrDocs 如何处理宏的文档注释?

MrDocs 通过 Clang 的 PPCallbacks 记录宏定义,并利用 LLVM 的更新(llvm/llvm-project#198452)获取宏的文档注释。之前是扫描源代码文本,现在通过 getRawCommentForAnyRedecl() 重载直接获取。同时,宏支持需要单独的过滤器(include-macros/exclude-macros)和 extract-all-macros 选项,因为宏名称无作用域,且宏没有成员。

类模板特化在文档中是如何展示的?

类模板特化现在显示在主模板页面的“Specializations”部分,推导指南显示在“Deduction Guides”部分,父作用域只列出主模板。孤立的特化(主模板被排除)仍保留在父列表中。这些信息通过 SpecializationFinalizer 填充,并作为元数据的一部分,供模板和下游消费者使用。

MrDocs 修复了哪些常见的文档生成问题?

修复的问题包括:部分特化数组类型渲染错误、task<> 丢失空参数列表、内联标记(如 <em>)不产生 HTML、@par 块渲染颠倒、HTML 表格导致致命错误、继承成员缺失、extract-all: false 崩溃、源链接指向 #Lundefined、长 noexcept 条件不可读、默认构造变量显示不存在的初始化器等。

Boost.StaticString 有哪些更新?

Boost.StaticString 新增了 basic_static_string::available() 方法,用于报告剩余容量。此外,修复了 basic_static_cstring 的比较运算符、compare(const CharT*) 对超长输入的处理,以及数组构造函数未遵守无内嵌 NUL 不变量的问题。

MrDocs 的安装页面如何避免提供过时的构建?

通过 #1181 修复,非标签包的名称在上传前会重命名为分支名,使得每次推送产生相同的文件名,发布操作会覆盖旧资产。标签发布保持版本化名称不变。这样避免了因版本号变化导致旧资产被误匹配的问题。

🏷️

标签

➡️

继续阅读