ARTICLE DETAIL

建站实战干货

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

miniblink49 仓库内 Google Test Pump 元编程工具手册:用 `foo.pump` 批量生成 C++ 模板与宏样板代码

2026/9/18 9:16:54 拓冰建站 浏览量
miniblink49 仓库内 Google Test Pump 元编程工具手册:用 `foo.pump` 批量生成 C++ 模板与宏样板代码 miniblink49 仓库内 Google Test Pump 元编程工具手册用foo.pump批量生成 C 模板与宏样板代码【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49PumpPump is Useful for Meta Programming / Pretty Useful for Meta Programming / Practical Utility for Meta Programming是 Google Test 附带的 C 元编程工具你只需写一个.pump源文件在其中混入少量以$开头的元代码就能自动展开成成百上千行结构重复的 C 模板、函数和宏。本手册以 V1_5_PumpManual.md 为骨架结合本仓库 pump.py 与真实.pump用例如 gtest-param-util-generated.h.pump展开讲解。读完本文你将掌握 Pump 的完整语法$var、$range、$for、$if、[[ ]]、$$注释等、运行方式、语法规则与实战技巧并能自己编写.pump文件来生成参数化、类型化的 C 代码。为什么需要 Pump模板库的“参数数量爆炸”问题模板库和宏库经常需要定义大量仅在参数个数上不同或几乎不同的类、函数或宏。例如 Google Test 的Values()参数化测试接口需要同时支持 1 个、2 个……直到 50 个参数每个参数个数对应一个ValueArrayN类Google Mock 的gmock-generated-*系列头文件要为 0 到 10 个参数的函数生成对应的 mock 辅助模板。这是大量重复、机械且极易出错的工作。变参模板variadic templates和变参宏variadic macros可以缓解这个问题但即便在编写本手册的年代它们尚未进入 C 标准、也未被编译器广泛支持尤其是在追求可移植性的代码中并不总是好选择而且能力仍然有限。于是库作者通常选择编写脚本来生成实现。但手写生成脚本本身也很繁琐脚本往往难以反映生成代码的结构、可读性和可维护性差生成代码里一个很小的改动可能需要在脚本中做一系列不直观、不小的调整——这在实验阶段尤其痛苦。Pump 正是为解决这一痛点而设计。Pump 的解决方案与设计亮点Pump 的思路很简单程序员写一个foo.pump文件其中既包含 C 代码又包含操纵这些 C 代码的元代码。元代码支持范围迭代、嵌套迭代、局部元变量定义、简单算术、条件表达式。你可以把它看作一个小型领域专用语言DSL元语言被刻意设计为“非侵入式”例如不会干扰 Emacs 的 C 模式且足够简洁使 Pump 源码直观易维护。Pump 的几个核心亮点单文件 Python 脚本极度可移植完整实现就在一个 pump.py 中无需构建、无需安装、跨平台直接运行。智能排版Pump 会尽量遵循 Google 代码风格指南在合适的位置断开超长行生成代码很容易超长使其不超过 80 列并正确缩进续行。比 XML 更人性化格式可读性好、更简洁。与 Emacs C 模式兼容良好。安装与运行零依赖一条命令Pump 无需安装。运行方式在 pump.py 的 docstring 中有明确说明USAGE: pump.py SOURCE_FILE EXAMPLES: pump.py foo.cc.pump Converts foo.cc.pump to foo.cc.也就是说foo.pump会被转换成同名的foo去掉.pump后缀文件。在仓库中你可以这样验证真实用例的生成结果例如将 gtest-param-util-generated.h.pump 重新生成为同名头文件。从源码结构看pump.py的处理管线分为三个阶段对应文件中的Tokenize、ParseToAST、RunCode等函数先按词法规则切分$var、$range、$for、$if、[[、]]等 Token再解析成 ASTVarNode、RangeNode、ForNode、IfNode、RawCodeNode、LiteralDollarNode等节点类最后在Env环境中求值并拼接输出。它识别元关键字的词法表TOKEN_TABLE依次为$var、$elif、$else、$for、$if、$range、$id、$($)、单独的$、[[与]]。语法速览三个 Token 概念与元注释在进入示例前先记住三个基本约定手册原文如此元关键字以$开头[[和]]是元括号meta brackets$$开启一条元注释注释持续到行尾。一个最小的“Hello”级示例展示了注释与变量$var n 3 $$ Defines a meta variable n.$$后面的内容全部是元注释不会出现在生成代码中。第一个完整示例生成 0 到 3 元的模板类手册给出的第一个完整示例同时用到了$var、$range、$for、$if/$elif/$else和嵌套迭代$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 编译器会将其翻译成如下 C 代码注意i的取值范围是闭区间0..n即 0、1、2、3// 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; };这个例子一次展示了四个关键行为$range i 0..n定义迭代变量i的范围..两侧都是闭区间端点且端点n引用前面$var定义的元变量$for i [[ ... ]]对i从 0 到 3 循环展开块内代码每次迭代把$i替换为当前值嵌套的$for j 1..i为每个Foo$i生成数量可变的模板参数typename A1, typename A2, ...$if/$elif/$else根据i的值选择不同的类成员代码。从 pump.py 的实现可以印证闭区间语义RunAtomicCode中ForNode的执行代码是for i in range(lower, upper 1):并且当i不是最后一个值时会在每次迭代之间追加分隔符sep。表达式求值则直接交给 Python 的evalEvalExp中result eval(exp.python_exp)所以i 0、i 2这类判断用的就是 Python 语法。第二个示例迭代分隔符的用法$for指令的完整形态是$for id sep [[code]]其中id与[[之间的文本就是每次迭代之间的分隔符。手册示例$range i 1..n Func($for i [[a$i]]); $$ The text between i and [[ is the separator between iterations.即迭代变量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...注意范围1..n的取值从 1 开始因此n为 0 时循环体一次都不执行只输出Func();。这正是 Google Test/Google Mock 头文件里template $for j, [[typename T$j]]这类写法的来源j与[[之间的,充当模板参数列表的分隔符。支持的元编程构造完整参考表手册将所有构造汇总如下表格已按 Markdown 规范整理构造含义$var id exp定义命名常量值。$id在当前元词法块meta lexical block结束前有效。$range id exp..exp设置迭代变量的范围之后可被多个循环复用。$for id sep [[code]]迭代。id的范围必须事先用$range定义。$id在code内有效。$($)生成一个$字符。$id命名常量或迭代变量的值。$(exp)表达式的值。$if exp [[ code ]] else_branch条件分支。[[ code ]]元词法块。cpp_code原始 C 代码原样透传。$$ comment元注释。重要的换行规则为了给用户排版自由Pump 会忽略紧跟$for foo之后、或紧邻[[/]]的换行符。如果没有这条规则你常常被迫写出非常长的行才能得到想要的输出。因此有时你需要在这些位置额外插入一个换行才能在输出中得到换行。正式文法Grammar手册给出 Pump 的完整文法定义code :: 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_syntax几个要点exp是Python 语法的简单表达式所以可以在$range、$if、$(...)中直接使用 Python 的算术与比较运算$var id [[ code ]]允许把一段展开后的代码一个元词法块赋值给元变量这是实现代码复用与拼接的关键手段else_branch支持链式的$elif文法中$elif exp [[ code ]] else_branch递归出现最终可以以$else收尾也可以为空。从源码角度印证pump.py中$var的解析确实分两种情形——右侧若紧跟[[则解析为一个代码块VarNode的atomic_code分支否则解析为一个表达式ParseExpNodeParseElseNode函数则专门处理$else与$elif的递归解析。真实用例Google Test / Google Mock 中的.pump文件本仓库的v8_7_5/testing目录下就保存着 Pump 在 Google Test 和 Google Mock 中的真实应用.pump文件会生成同名去掉后缀的头文件gtest-param-util-generated.h.pump生成gtest-param-util-generated.hgtest-type-util.h.pumpgtest-tuple.h.pumpgtest-param-test.h.pumpgmock-generated-actions.h.pumpgmock-generated-function-mockers.h.pumpgmock-generated-matchers.h.pumpgmock-generated-nice-strict.h.pumpgmock-generated-internal-utils.h.pumpgmock-generated-actions.h.pump以 gtest-param-util-generated.h.pump 为例文件开头就定义了生成规模参数$$ -*- mode: c; -*- $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()最多支持 50 个参数Combine()最多支持 10 个参数后者同时受 tuple 实现的最大元数限制。随后用$range i 1..n、$for i加上内层$range j 1..i、$for j批量生成ValueArray1到ValueArray50的类模板$range i 1..n $for i [[ $range j 1..i template $for j, [[typename T$j]] class ValueArray$i { public: $if i1 [[explicit ]]ValueArray$i($for j, [[T$j v$j]]) : $for j, [[v$(j)_(v$j)]] {} ...这里能看到前面所有构造的综合运用外层$for i枚举参数个数内层$for j生成参数列表$if i1控制单参数构造函数是否加explicit$for j, [[...]]中的,作为分隔符拼接成员初始化列表。同时注意$($)转义的需求文件末尾的版权声明、宏名GTEST_INCLUDE_...中的真实$并不存在但若你需要在生成的 C 代码中输出美元符号就必须使用$($)。对应的生成结果头文件如 gmock-generated-function-mockers.h第二行会留下生成记录// pump.py gmock-generated-function-mockers.h.pump正文开头还有// This file is generated by a SCRIPT. DO NOT EDIT BY HAND!的警告——这正是 Pump 生成流水线的指纹改.pump、跑pump.py而不是手改生成物。README.md 也明确建议修改这些由脚本生成的头文件时应修改对应的.pump文件并用pump.py重新生成Makefile.am 中同样把scripts/pump.py列为发行内容的一部分说明它作为构建链一环的正式地位。实用技巧Tips手册最后给出两条高频实用技巧变量与字母/数字粘连当元变量后面紧跟字母或数字时用[[]]插入空字符串来分隔。例如Foo$j[[]]Helper在j为 1 时生成Foo1Helper。[[与]]之间是空代码块展开结果为空串恰好起到“分词符”作用。避免超长源码行想在任意位置断行时插入[[]]后接换行即可。由于紧邻[[或]]的换行符会被忽略生成的代码不会包含这个换行从而既保持了.pump源文件的可读性又不会污染输出。小结与上手路径Pump 的核心价值一句话概括把“参数个数维度”上的机械重复交给元代码让手写的部分只保留真正的逻辑差异。它的语法面很小$var/$range/$for/$if/$elif/$else/[[ ]]/$($)/$$表达式直接使用 Python 语法实现是单文件 Python 脚本因而极易嵌入任何构建流程。如果你想亲手实践推荐的上手路径通读本手册与 PumpManual.md仓库内更新的同主题手册对照 pump.py 的 docstring其中内嵌了 USAGE 和完整 GRAMMAR打开 gtest-param-util-generated.h.pump 这类真实文件体会元代码与 C 代码如何交织新建一个foo.pump尝试用$range 嵌套$for生成不同元数的模板类再用python pump.py foo.pump检查输出是否符合预期。当你需要为 C 库生成“参数个数变化”的模板、函数或宏而又不想维护脆弱的手写脚本时Pump 是一个经过 Google Test / Google Mock 实战检验的轻量选择。【免费下载链接】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),仅供参考