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/miniblink49PumpPretty Useful for Meta Programming是 Google Test 项目内置的一个轻量级 C 元编程工具开发者编写一个内嵌元指令的.pump文件Pump 将其编译为纯 C 源码从而把仅参数个数不同的大量重复类、函数与宏的编写工作自动化。本文以本仓库随附的 Pump 使用手册 为主线结合 pump.py 源码 与 Google Test 中真实应用的.pump文件讲解 Pump 的元语法、完整文法、命令行用法及其底层工作原理读完即可在自己的项目中用 Pump 编写可维护的代码生成脚本。问题背景为什么需要元编程工具模板库和宏库经常需要定义大量仅在参数个数上不同或几乎仅在此不同的类、函数或宏。例如template typename T1、template typename T1, typename T2、template typename T1, typename T2, typename T3……这是一项重复、机械且极易出错的工作。变参模板variadic templates与变参宏variadic macros可以缓解该问题但在 Pump 诞生的年代二者都尚未被 C 标准收录、也未得到编译器广泛支持对追求可移植性的代码而言往往不是好选择且能力仍然有限。因此这类库的作者通常不得不编写脚本去生成实现代码。但实践证明手工编写这类脚本同样痛苦——脚本往往难以反映生成代码的结构、可读性差、难以编辑生成代码需要一处小改动脚本往往就要跟着做不直观的大改动这在代码实验迭代阶段尤为折磨人。我们的方案Pump 是什么Pump 是一个面向 C 的简单元编程工具。核心思路是程序员编写一个foo.pump文件其中包含 C 代码以及操纵这些 C 代码的元代码。元代码支持区间迭代iteration over a range嵌套迭代nested iterations局部元变量定义local meta variable definitions简单算术simple arithmetic条件表达式conditional expressions可以把它看作一个小型领域特定语言DSL。元语言被刻意设计为非侵入式例如不会干扰 Emacs 的 C 模式且简洁使 Pump 代码直观、易于维护。特性亮点实现为单个 Python 脚本因此超级可移植无需构建、无需安装跨平台直接运行Pump 会智能对齐 [Google 代码风格指南]80 列限制在合适位置折断过长生成代码很容易过长的行并正确缩进续行格式对人类可读且比 XML 更简洁格式与 Emacs 的 C 模式配合良好。核心语法构造速查表Pump 中元关键字以$开头[[与]]是元代码块括号$$开始一条以行尾结束的元注释。支持的元编程构造如下构造含义$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之后、以及紧邻[[或]]的换行符。若没有这条规则用户往往被迫写超长行才能得到想要的输出。因此有时需要在上述位置额外插入一个换行才能让换行出现在输出中。完整示例一生成 Foo0 到 Foo3 四个类下面这段 Pump 代码元关键字以$开头[[/]]为元括号$$为元注释定义了元变量n声明迭代器i的范围并在$for元循环中嵌套了$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 编译器转换后得到// 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 j [[, typename A$j]]中的$for j引用了内部$range j 1..i定义的范围且每次外层迭代都会重新定义内层范围$if的$elif i 2与$else给出了多分支条件生成的注释// Foo$i does blah for $i-ary predicates.也会随迭代展开说明元代码可以穿插在注释中。完整示例二分隔符控制参数列表$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...完整文法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 语法的简单表达式Pump 借助 Python 的eval求值atomic_code之间自由排列code就是若干原子代码的串联$var既支持$var id exp的表达式形式也支持$var id [[ code ]]的代码块形式后者把一段展开后的代码文本作为变量值。仓库源码级实现原理Pump 的实现集中在单个 Python 脚本 pump.py文档原链接../scripts/pump.py对应仓库根路径为v8_5_7/testing/gtest/scripts/pump.py。脚本标注版本为pump v0.2.0作者为 Zhanyong Wan采用 BSD 许可。其工作流程是典型的词法分析 → 语法分析 → AST 求值 → 输出美化四阶段词法分析TOKEN_TABLEpump.py 用一张正则驱动的TOKEN_TABLE完成分词包括$var、$elif、$else、$for、$if、$range、$id\$[_A-Za-z]\w*、$($)、单独的$、[[含紧随的换行\[\[\n?与]]含紧随的换行。注意[[]]这类写法在正则层面就被允许带换行这正是文档换行会被忽略规则的实现基础。语法分析ParseToASTToken 流经 ParseToAST 递归下降解析为 AST节点类型与文法一一对应CodeNode、VarNode、RangeNode、ForNode、ElseNode、IfNode、RawCodeNode、LiteralDollarNode、ExpNode。其中表达式节点会先把标识符重写为self.GetValue(\1)形式见 ParseExpNode从而把$n、$i这样的元标识符映射到运行时环境。求值执行Env 与 RunCode执行阶段由 Env 维护作用域栈variables与ranges都以压栈到头部的方式管理Clone()在进入子作用域时复制环境从而支持嵌套迭代与局部变量遮蔽。几个值得注意的实现细节PushVariable 会尝试把看起来像整数的值转为int存储转换失败则保留原字符串这是$if i 0这类数值比较能成立的原因迭代执行在 RunAtomicCode 的 ForNode 分支对range(lower, upper 1)循环每次迭代克隆环境并压入迭代变量除最后一次迭代外在每次迭代输出之间追加sep分隔符——这正是示例二Func(a1 a2)中分隔符的生成逻辑$var的代码块形式在 VarNode 分支先用子输出缓冲运行块内代码、把结果字符串作为变量值压栈。输出美化BeautifyCode生成的代码会经过 BeautifyCode 处理逐行调用 WrapLongLine对超过 80 列的行执行智能折行——单行注释用 WrapComment前缀//按词边界折行普通代码用 WrapCode优先在,、;后折行续行缩进 4 空格预处理指令用\续行符拼接。同时它特别豁免了头文件卫士#ifndef/#define/#endif、#include行与 IWYU pragma 不参与折行。命令行入口pump.py 的 main 函数 定义了两种用法pump.py foo.cc.pump把.pump后缀去掉得到输出文件foo.cc并在文件头写入生成信息// This file was GENERATED by command: // pump.py foo.cc.pump // DO NOT EDIT BY HAND!!!输入文件不带.pump后缀时生成结果打印到标准输出不带任何参数时打印工具用法文档。这也是 Google Test 惯例foo.h.pump生成foo.h的自动化实现生成文件自带勿手工编辑警告头改动一律改.pump源文件再重新生成。仓库中的真实应用Google Test 的 .pump 文件Pump 并非玩具示例Google Test 的类型化测试基础设施正是由 Pump 生成的。本仓库V8 5.7 内置的 Google Test 1.7 版随附了四个真实的.pump源文件gtest-tuple.h.pump文件开头$var n 10 $$ Maximum number of tuple fields we want to support.把元变量n定为 10随后用三个顶层$range i 0..n-1、$range j 0..n、$range k 1..n与多个$for循环展开GTEST_n_TUPLE_、GTEST_n_TYPENAMES_宏并逐一生成template typename T1 void, ...的tuple特化、构造函数、make_tuple()工厂与tuple_size特化。其生成的 gtest-tuple.h 就是实际参与编译的头文件gtest-type-util.h.pump$var n 50即最多支持 50 个类型的类型列表与类型参数化测试用例文件头同样注明This file is generated by a SCRIPT. DO NOT EDIT BY HAND!gtest-param-test.h.pump 与 gtest-param-util-generated.h.pump为参数化测试生成Values、Range、ValuesIn等 0~N 参数的调用适配代码。可以看出 Pump 的典型应用场景同一份模板逻辑需要为 0 到 N 个参数各生成一份完整实现——用手写维护代价极高而用 Pump 只需维护一份带元循环的源文件。这也是为什么本仓库的 V8 构建依赖里 Google Test 会同时携带.pump源文件与生成后的.h文件gen/下的生成产物配合testing/gtest的源文件共同支撑测试基础设施。实用技巧元变量后紧跟字母或数字时的分隔使用[[]]插入空字符串隔开。例如Foo$j[[]]Helper在j为 1 时生成Foo1Helper避免超长源行可在任意位置插入[[]]后换行。由于紧邻[[/]]的换行会被忽略生成代码中不会出现该换行从而兼顾源码排版与输出格式。局限性与适用前提元表达式是 Python 语法运行环境必须安装 PythonPump 是单文件 Python 2 风格脚本当前仓库版本未提供 Python 3 适配运行时以仓库内脚本实际行为为准文档与脚本均注明它还很粗糙、缺少自动化测试使用前建议先在小型.pump文件上验证输出生成结果依赖BeautifyCode的 80 列折行策略若你的项目不使用 Google 风格 80 列规范需自行评估输出格式是否符合预期原文档提到的 Google Code Search 在线示例服务早已下线但本仓库随附的四个.pump文件见上文就是最直接、可离线研读的真实案例。结语Pump 以约 850 行 Python 代码实现了带元循环、条件与变量的代码生成 DSL并在 Google Test 中承担了 tuple、类型工具与参数化测试基础设施的全部样板生成工作。它的核心设计——非侵入的$元语法、Python 表达式、[[]]空串技巧、自动 80 列美化——对今天任何需要按参数个数批量生成 C 代码的场景模板库、宏库、序列化代码等依然有直接借鉴价值。上手路径很简单通读 V1_7_PumpManual.md 掌握语法对照 pump.py 理解实现再以 gtest-tuple.h.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),仅供参考
