ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

miniblink49 内置 Google Test 的 Pump 元编程工具手册:从 .pump 源码生成 C++ 模板代码

2026/9/17 17:29:28 拓冰建站 浏览量
miniblink49 内置 Google Test 的 Pump 元编程工具手册:从 .pump 源码生成 C++ 模板代码 miniblink49 内置 Google Test 的 Pump 元编程工具手册从 .pump 源码生成 C 模板代码【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49导读Pump 是 Google Test 团队开发的一个轻量级 C 元编程meta-programming工具它通过一种内嵌在 C 代码中的小型领域专用语言DSL把仅参数个数不同、其余几乎重复的模板、宏和类一次性生成出来免去大量机械且易错的复制粘贴工作。在 miniblink49 仓库随 V8 7.5 一起内置的 Google Test 中Pump 正是Values()、Combine()、tuple等 API 得以支持1 到 50 个参数的幕后功臣。读完本文你将掌握 Pump 的完整语法、运行方式、底层实现原理以及如何在 v8_7_5/testing/gtest 中基于真实.pump文件完成代码再生成。问题背景模板库的参数个数诅咒模板库和宏库经常需要定义大量仅在参数个数上不同的类、函数或宏。例如 Google Test 的Values(v1, v2, ..., vN)参数生成器需要为 1 到 50 个参数各写一份重载tuple需要为 0 到 10 个字段各写一份特化。这是海量重复、机械且极易出错的工作。可变参数模板variadic templates和可变参数宏variadic macros虽然能缓解这个问题但编写该文档时它们都还未进入 C 标准、也未被编译器广泛支持移植性差能力也仍然有限详情见 V1_7_PumpManual.md。于是这类库的作者通常会写脚本去生成实现。但脚本往往难以反映生成代码的结构可读性差、难编辑生成代码里一个很小的改动可能要求脚本做大量不直观、不平凡的修改实验迭代时尤其痛苦。我们的解决方案Pump——为元编程而生PumpPump is Useful for Meta Programming / Pretty Useful for Meta Programming / Practical Utility for Meta Programming三种解释随你喜好是一个简单的 C 元编程工具。程序员编写一个foo.pump文件里面同时包含 C 代码和操控这些 C 代码的元代码meta code。元代码支持对某个区间做迭代含嵌套迭代局部元变量定义简单算术条件表达式。你可以把它看作一个小型领域专用语言。元语言被刻意设计得非侵入式例如不会干扰 Emacs 的 C 模式且简洁使 Pump 代码直观、易于维护。设计亮点实现只有单个 Python 脚本超强可移植无需构建、无需安装跨平台直接运行尽量遵循 Google 代码风格规范自动在合适位置折断超长行生成代码很容易超长控制在 80 列以内并正确缩进续行格式人类可读比 XML 更简洁与 Emacs 的 C 模式配合良好。快速上手运行 pump.pyPump 的实现位于 scripts/pump.py。该文件自带完整的使用说明见 pump.py#L32-L63USAGE: pump.py SOURCE_FILE EXAMPLES: pump.py foo.cc.pump Converts foo.cc.pump to foo.cc.即pump.py 源码文件输出文件为去掉.pump后缀的同名文件。当输入文件名不以.pump结尾时结果打印到标准输出output_file_path -见 pump.py#L838-L843。需要说明的是脚本使用 Python 2 语法如print语句、file()内建函数在当前仓库环境中运行前请确认所用解释器版本。当输出是文件时pump.py 会在生成文件头部写入一段生成告警例如仓库中真实的 gtest-tuple.h 开头就是// This file was GENERATED by command: // pump.py gtest-tuple.h.pump // DO NOT EDIT BY HAND!!!这一约定在 README.md 中也有明确说明正常情况下无需担心重新生成源码文件除非你需要修改它们此时应修改对应的.pump文件再运行 pump.py 脚本重新生成。元语言速览从示例读懂 PumpPump 的元关键字以$开头[[与]]是元代码块定界符$$开启一条元注释到行尾结束。下面这段完整示例节选自 V1_7_PumpManual.md同时演示了元变量、区间、循环和条件$var n 3 $$ Defines a meta variable n. $range i 0..n $$ Declares the range of meta iterator i (inclusive). $for i [[ $$ Meta loop. // Foo$i does blah for $i-ary predicates. $range j 1..i template size_t N $for j [[, typename A$j]] class Foo$i { $if i 0 [[ blah a; ]] $elif i 2 [[ blah b; ]] $else [[ blah c; ]] }; ]]经 Pump 编译器翻译后得到// Foo0 does blah for 0-ary predicates. template size_t N class Foo0 { blah a; }; // Foo1 does blah for 1-ary predicates. template size_t N, typename A1 class Foo1 { blah b; }; // Foo2 does blah for 2-ary predicates. template size_t N, typename A1, typename A2 class Foo2 { blah b; }; // Foo3 does blah for 3-ary predicates. template size_t N, typename A1, typename A2, typename A3 class Foo3 { blah c; };注意$if/$elif/$else的分支选择Foo0落入i 0分支生成blah a;Foo1、Foo2落入i 2分支生成blah b;Foo3落入$else分支生成blah c;。再看迭代分隔符的用法节选自 V1_7_PumpManual.md$range i 1..n Func($for i [[a$i]]); $$ The text between i and [[ is the separator between iterations.$for i与[[之间的就是各次迭代之间的分隔符。根据n的值会生成Func(); // If n is 0. Func(a1); // If n is 1. Func(a1 a2); // If n is 2. Func(a1 a2 a3); // If n is 3. // And so on...元编程构造总览Pump 支持的完整元编程构造如下表完整继承自 V1_7_PumpManual.md#L120-L132构造说明$var id exp定义具名常量值。$id在当前元词法块meta lexical block结束前一直有效。$range id exp..exp设置迭代变量的区间该变量可在之后的多个循环中复用。$for id sep [[ code ]]迭代。id的区间必须已事先定义。$id在code中有效。$($)生成一个单独的$字符。$id具名常量或迭代变量的值。$(exp)表达式的值。$if exp [[ code ]] else_branch条件分支。[[ code ]]元词法块。cpp_code原样透传的 C 代码。$$ comment元注释。换行规则说明为了给用户在排版 Pump 源码时留出自由度Pump 会忽略紧跟$for foo之后、或紧邻[[/]]的换行符。没有这条规则你往往会被迫写出超长行才能得到期望的输出。因此若你确实希望这些位置出现换行有时需要额外插入一个换行符。Pump 文法Pump 的完整文法如下完整继承自 V1_7_PumpManual.md#L143-L160code :: atomic_code* atomic_code :: $var id exp | $var id [[ code ]] | $range id exp..exp | $for id sep [[ code ]] | $($) | $id | $(exp) | $if exp [[ code ]] else_branch | [[ code ]] | cpp_code sep :: cpp_code | empty_string else_branch :: $else [[ code ]] | $elif exp [[ code ]] else_branch | empty_string exp :: simple_expression_in_Python_syntaxexp即Python 语法中的简单表达式——这一点在 pump.py#L62 的文档字符串中有同样的声明也决定了 Pump 元表达式直接复用 Python 的算术与比较语义。源码级原理pump.py 是如何把元代码变成 C 的scripts/pump.py 的实现虽然只有几百行却走了一条完整词法 → 语法 → 求值 → 美化的编译流水线。词法阶段TOKEN_TABLE词法器通过正则表达式表pump.py#L72-L84识别所有元关键字TOKEN_TABLE [ (re.compile(r\$var\s), $var), (re.compile(r\$elif\s), $elif), (re.compile(r\$else\s), $else), (re.compile(r\$for\s), $for), (re.compile(r\$if\s), $if), (re.compile(r\$range\s), $range), (re.compile(r\$[_A-Za-z]\w*), $id), (re.compile(r\$\(\$\)), $($)), (re.compile(r\$), $), (re.compile(r\[\[\n?), [[), (re.compile(r\]\]\n?), ]]), ]注意两个细节[[/]]的正则都允许后随一个可选的\n这正是上一节换行规则在实现层的体现\$\$元注释并不在 token 表里而是在 StripMetaComments 中于解析前被整体剥离先删掉整行只有注释的行再删除内容行行尾的注释。语法阶段从 Token 到 ASTTokenizepump.py#L382-L387把源码流式切成 tokenParseToASTpump.py#L577-L581再按文法递归下降构造出由CodeNode、VarNode、RangeNode、ForNode、IfNode、RawCodeNode、LiteralDollarNode、ExpNode组成的 AST节点定义见 pump.py#L390-L441。其中ParseExpNode会把表达式里的每个标识符\w改写为self.GetValue(\1)pump.py#L470-L472从而把元变量解析连接到求值环境。求值阶段Env 环境与递归展开Env类pump.py#L584-L638维护元变量栈与区间栈GetValue在未定义元变量时报错退出。RunAtomicCodepump.py#L656-L699按节点类型递归执行VarNode先求值子代码块再把结果PushVariable入栈RangeNode把表达式求值为整型后PushRangeForNodefor i in range(lower, upper 1)闭区间迭代每次把i压入变量栈后递归展开循环体非末次迭代时追加分隔符seppump.py#L668-L680IfNodeeval条件表达式为真展开 then 分支否则展开else_branchExpNode把求值结果字符串化输出LiteralDollarNode原样输出$。由于变量/区间入栈都是入栈到头部 递归时 Clone 环境见Env.Clone嵌套作用域天然隔离内层循环结束后外层变量不受影响。美化阶段BeautifyCode 与 80 列折行展开后的原始文本再经BeautifyCodepump.py#L814-L820逐行处理。WrapLongLinepump.py#L790-L811按行内容分派普通代码行超 80 列时优先在,或;后折行续行缩进 4 格WrapCodepump.py#L741-L768单行注释超长时按单词折行并保持//前缀对齐WrapCommentpump.py#L717-L738预处理器指令用行尾反斜杠续行WrapPreprocessorDirectivepump.py#L771-L772头文件守卫、#include、IWYU pragma 等按风格规范特例放行不折行。整条流水线汇聚在 ConvertFromPumpSource先剥注释、再解析 AST、然后求值展开、最后美化输出。仓库中的真实案例Google Test 的 4 个 .pump 文件当前仓库的 Google Test 共有 4 个.pump源文件它们在 Makefile.am 中被明确登记为分发源include/gtest/gtest-param-test.h.pumpinclude/gtest/internal/gtest-param-util-generated.h.pumpinclude/gtest/internal/gtest-tuple.h.pumpinclude/gtest/internal/gtest-type-util.h.pump案例一gtest-param-test.h.pump —— 参数生成器重载的批量生产gtest-param-test.h.pump 开头用两个元变量声明容量上限第 2-3 行$var n 50 $$ Maximum length of Values arguments we want to support. $var maxtuple 10 $$ Maximum number of Combine arguments we want to support.Values()的 1..50 参数重载由双重循环生成第 346-355 行$range i 1..n $for i [[ $range j 1..i template $for j, [[typename T$j]] internal::ValueArray$i$for j, [[T$j]] Values($for j, [[T$j v$j]]) { return internal::ValueArray$i$for j, [[T$j]]($for j, [[v$j]]); } ]]这里$for j, [[...]]中的,就是迭代分隔符最终展开出Values(T1 v1)、Values(T1 v1, T2 v2)……直到Values(T1 v1, ..., T50 v50)的 50 个重载。同理Combine()的 2..10 个生成器重载由$range i 2..maxtuple驱动生成第 430-441 行。这正是改一处元代码即可整体调整重载数量的典型收益想把上限从 50 提到 60只改$var n 50一行。案例二gtest-tuple.h.pump —— 宏与模板的联合展开gtest-tuple.h.pump 用$var n 10声明 tuple 字段上限并同时声明了三条区间第 64-66 行$range i 0..n-1 $range j 0..n $range k 1..n随后用$for k循环生成GTEST_0_TUPLE_~GTEST_10_TUPLE_等宏定义第 70-84 行并用$for i [[typename T$i void]]一次性生成tuple主模板的 0..9 个类型参数第 92-93 行。这里还能看到两个有意思的细节GTEST_$(n)_TYPENAMES_(U)这种写法是元变量后紧跟字母/数字时用[[]]分隔技巧的变体$()显式取表达式值生成出的 gtest-tuple.h 头部明确写着由pump.py gtest-tuple.h.pump生成、禁止手改。再生成工作流改 .pump 而非改 .h按照 README.md 与 DevGuide.md 的约定修改这类生成代码的正确流程是编辑对应的.pump源文件例如调整$var n 50的参数上限或修改循环体模板在 scripts 目录下运行pump.py 文件名.pump重新生成同名.h文件检查生成文件头部的// This file was GENERATED by command:注释确认生成命令正确然后照常提交。使用技巧以下技巧同样来自 V1_7_PumpManual.md#L174-L178在实际编写.pump文件时非常实用变量与字母数字粘连时用[[]]分隔如果元变量后面紧跟字母或数字可以用[[]]插入一个空字符串来隔开。例如Foo$j[[]]Helper在j为 1 时生成Foo1Helper。否则$jHelper会被词法器按\$\w规则整体吞掉见 TOKEN_TABLE 中的$id规则从而解析失败。用[[]] 换行自由断行为了避免 Pump 源码出现超长行可以在任意位置插入[[]]后换行。由于紧邻[[/]]的换行符会被忽略生成代码中不会出现这个换行因此不会污染输出格式。小结Pump 用元代码 C 代码混排的方式把参数个数 N 变化这一类 C 模板/宏库的经典痛点收敛成一套极其轻量的可移植解决方案单文件 Python 实现、无构建无安装、语法直观、输出自动对齐 80 列风格规范。在 miniblink49 内置的 Google Test 里Values()的 50 档重载、Combine()的 10 档笛卡尔积、tuple的 10 字段支持全部来自 scripts/pump.py 与仓库内 4 个.pump源文件Makefile.am 中均有登记。若你在其他项目里也遇到仅参数个数不同的重复代码Pump 这份实践是一份非常值得参考的模板先写脚本生成再让脚本服务于易维护的声明式元语言。【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考