miniblink49 仓库中的 C++ 元编程利器:Pump 语言手册(V1.6)与源码级实战解析 miniblink49 仓库中的 C 元编程利器Pump 语言手册V1.6与源码级实战解析【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49PumpPretty Useful for Meta Programming是 Google Test / Google Mock 团队开发的一款面向 C 的预处理器元编程小工具程序员在一个.pump文件中混写 C 代码与元代码由 Pump 编译器一次性展开出大量重复的模板、宏与类定义。本手册正文取自当前仓库 V1_6_PumpManual.md并以仓库内随附的 pump.py 实现与数十个真实.pump源文件为佐证。读完本文你将掌握 Pump 的完整语法、条件/循环/嵌套展开的写法、行宽美化规则以及它如何支撑 gtest/gmock 中高达 50 元参数的重型模板代码生成。一、它要解决的问题模板与宏的机械重复模板库和宏库经常需要定义大量仅在参数个数上不同的类、函数或宏。例如一个接受任意元数arity参数的 tuple、一个支持 0~10 个构造函数参数的单例包装都必须为每个元数手写一份几乎相同的代码——这是大量重复、机械且极易出错的工作。C 委员会当时正在考虑可变参数模板variadic templates与可变参数宏variadic macros但两者既未进入标准也未被编译器广泛支持且能力仍然有限尤其不适合需要跨平台移植的代码。因此这类库的作者往往选择写脚本去生成实现。但手写脚本的痛点在于脚本结构往往不能反映生成代码的结构难读、难改——生成代码中一个小小的改动可能要求脚本里做出非常不直观的修改这在代码实验阶段尤为痛苦。二、Pump 的解决思路让生成代码的结构直接可见Pump 的核心理念是程序员编写一个foo.pump文件其中同时包含 C 代码与操纵这些 C 代码的元代码。元代码支持对数值范围的迭代嵌套迭代局部元变量定义简单算术条件表达式可以把它看作一个小型领域特定语言DSL。它的元语言被刻意设计为非侵入式例如不会干扰 Emacs 的 C 模式高亮且简洁使 Pump 代码直观、易于维护。Highlights设计亮点实现是单个 Python 脚本因此超便携无需构建、无需安装跨平台即开即用见 scripts/pump.py。遵循 Google 代码风格指南Pump 会自动在可接受的位置折断超长行生成代码很容易超长使其不超过 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.关键行为对应 pump.py 的main()函数第 831 行起若输入文件以.pump结尾生成文件为去掉后缀的同名文件foo.cc.pump→foo.cc若输入不以.pump结尾结果直接打印到标准输出-写入文件时会自动在文件头部追加三行声明// This file was GENERATED by command: // pump.py gtest-tuple.h.pump // DO NOT EDIT BY HAND!!!这一点在仓库中可以得到直接验证gtest-tuple.h 的前四行正是这样的生成标记意味着所有手改生成文件的修改都会在下次重新生成时丢失——正确做法是修改.pump源文件再重新运行脚本。仓库自带的 README.md 也明确说明正常情况下无需操心重新生成源码只有当需要修改时才应修改对应的.pump文件并运行 pump.py。兼容性提示从源码结构判断pump.py使用 Python 2 语法print语句、内建file()需要 Python 2 环境或相应兼容层才能直接运行。四、Pump 语言核心构造ConstructsPump 的元关键字以$开头[[与]]是元括号meta brackets$$开启一条到行尾的元注释。官方手册给出完整构造表构造含义$var id exp定义命名常量$id在当前元词法块结束前有效$range id exp..exp设定迭代变量的取值范围可被后续多个循环复用$for id sep [[ code ]]迭代id的范围必须已定义$id在code内有效$($)生成单个$字符$id命名常量或迭代变量的值$(exp)表达式的值$if exp [[ code ]] else_branch条件分支[[ code ]]元词法块cpp_code原样输出的 C 代码$$ comment元注释格式自由度说明为了给用户排版自由Pump 会忽略紧跟$for foo之后、或紧邻[[/]]的换行符。没有这条规则你往往被迫写出超长行才能得到期望输出。因此在上述位置如果需要输出换行有时要额外插入一个空行。五、Pump 语言的文法Grammar手册给出了完整的 BNF 风格文法方便实现或深入理解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 语法的简单表达式——这也是 Pump 能免费获得算术与比较能力的原因见下文源码剖析。六、完整示例一循环 嵌套范围 条件分支手册给出的第一个示例完整演示了$var、$range、$for、$if/$elif/$else的配合。以下 Pump 源码$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; };可见外层$for i控制展开 4 次内层$for j根据当前$i动态决定模板参数个数$if/$elif/$else按$i的值选择不同的类体。这正是脚本结构直接反映生成代码结构的体现。七、完整示例二迭代分隔符separator第二个示例演示$for的分隔符机制——位于循环变量与[[之间的文本是每次迭代之间的连接符$range i 1..n Func($for i [[a$i]]); $$ The text between i and [[ is the separator between iterations.根据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...也就是说$for i [[a$i]]中i与[[之间的被用作迭代间分隔符最后一次迭代之后不输出分隔符。这是生成逗号分隔的模板参数列表加号连接的表达式这类代码的惯用法。八、仓库中的真实应用gtest / gmock 的 .pump 源文件手册提到 Pump 在 Google Test 与 Google Mock 中有大量真实应用foo.h.pump生成foo.h。这一点在当前仓库可以得到完整印证——在v8_7_5/testing下随附了完整的.pump源文件与对应的生成产物gtest 侧生成测试框架头文件gtest-tuple.h.pump →gtest-tuple.h顶部$var n 10最大支持 10 字段 tuple用三层$range/$for展开GTEST_k_TUPLE_、GTEST_k_TYPENAMES_宏、tuple模板的 1~10 元构造函数、make_tuple、getk与operator/!的完整实现gtest-param-test.h.pump →gtest-param-test.h$var n 50Values(...)参数个数上限、$var maxtuple 10Combine(...)组合个数上限支撑Range/Values/ValuesIn/Bool/Combine系列参数生成器gtest-param-util-generated.h.pump 与 gtest-type-util.h.pump 同属此类。gmock 侧生成 mock 辅助模板gmock-generated-nice-strict.h.pump$var n 10定义最大元数外层$for kind 0..2依次生成NiceMock / NaggyMock / StrictMock三个类模板且三个类名/策略名本身也是由$if kind0/1/else元代码算出来的gmock-generated-actions.h.pump、gmock-generated-function-mockers.h.pump、gmock-generated-matchers.h.pump 及internal下的两个.pump文件共同构成 gmock 的生成代码体系。构建脚本 Makefile.am 将上述.pump文件与scripts/pump.py一并列入发布清单而对应的生成头文件如 gtest-tuple.h第一行就带着pump.py gtest-tuple.h.pump的生成命令标记形成源文件——生成器——产物的完整证据链。以gtest-tuple.h.pump中最有代表性的一段为例用嵌套范围生成GTEST_k_TUPLE_(T)宏tuple 类型最多n个字段不足部分用void补足$range k 1..n $for k [[ $range m 0..k-1 $range m2 k..n-1 #define GTEST_$(k)_TUPLE_(T) tuple$for m, [[T##$m]]$for m2 [[, void]] ]]这行元代码同时用到了迭代范围复用k的 range 在最外层定义一次、表达式求值$(k)、迭代分隔符,、嵌套范围m与m2互补——是手册全部核心语法的综合实战。九、源码级剖析pump.py 是如何工作的scripts/pump.pydocstring 自述版本pump v0.2.0 - Pretty Useful for Meta Programming整体是一条清晰的三阶段流水线词法分析TokenizeTOKEN_TABLE第 72 行定义了按优先级匹配的正则表——$var、$elif、$else、$for、$if、$range、$id、$($)、裸$、[[、]]。值得注意的是[[\n?与]]\n?两条正则[[/]]后的可选换行被直接吞掉这正是手册中忽略紧邻元括号的换行规则的实现。语法分析ParseParseToAST按文法把 token 流组装成VarNode / RangeNode / ForNode / IfNode / ElseNode / RawCodeNode / LiteralDollarNode / ExpNode构成的 AST。表达式节点在ParseExpNode中通过正则re.sub(r([_A-Za-z]\w*), rself.GetValue(\1), token.value)把标识符替换为self.GetValue(...)调用——这就是exp可以直接使用 Python 语法的底层机制。求值Run / EvalEnv类维护variables与ranges两个栈RunAtomicCode递归执行各节点ForNode依据GetRange得到的(lower, upper)闭区间循环并在非末次迭代后追加分隔符sepVarNode若值是[[ code ]]块会先在新环境中求值该块再入栈支持元变量存代码片段IfNode对表达式求布尔值后选择分支。生成文本最终交给BeautifyCode第 814 行做行宽美化WrapLongLine对超过 80 列的行分类处理——单行//注释按单词切分并以indent // 前缀续行WrapComment第 717 行预处理指令#开头或上一行以\结尾续行时追加\WrapPreprocessorDirective第 771 行普通代码优先在,或;后折行续行缩进在原缩进基础上加 4 个空格超长片段再在空格处兜底折断WrapCode第 741 行头文件保护宏#ifndef/#define/#endif、#include与 IWYU pragma 属于例外不折断。另外StripMetaComments第 226 行在解析前先剔除$$元注释整行注释连同行尾换行一并删除行内注释从$$起截断——所以元注释永远不会泄漏进生成代码。十、实用技巧Tips手册最后给出两条高频技巧元变量后紧跟字母或数字时的分隔用[[]]插入空字符串即可例如Foo$j[[]]Helper在j为 1 时生成Foo1Helper若直接写Foo$jHelperjHelper会被整体识别为变量名。避免超长的 Pump 源码行在任意位置插入[[]]并换行即可断行。由于紧邻[[或]]的换行会被忽略生成代码中不会出现这个多余换行——既保住了源码可读性又不污染输出。十一、结语Pump 是用脚本生成 C 代码这一思路的优雅实现单文件 Python、语法极简、输出自带 80 列美化且生成代码结构与 Pump 源码结构高度一致便于长期维护。在 miniblink49 仓库的v8_7_5/testing目录下你可以对照 Pump 手册 与 V1.5/V1.6/V1.7 各版手册结合 pump.py 和十余个真实.pump文件完整复现 gtest/gmock 中那些动辄数十元参数的重型模板与宏的生成过程。理解 Pump也就理解了 gtest/gmock 源码中大量看起来像手写的代码真正的来源与再生成方式。【免费下载链接】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),仅供参考