- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
Pump("Pump isUseful forMetaProgramming",也常写作 Pretty Useful for Meta Programming)是 GoogleTest/Google Mock 附带的一个 C++ 元编程代码生成工具。它允许开发者编写一份.pump源文件,其中混写 C++ 代码与极简的元代码指令,由 Pump 编译器展开为完整、可读的 C++ 头文件,从而彻底告别"为 0 到 N 个参数手写 N 份几乎相同的模板类/宏"这类机械且易错的重复劳动。本文以本仓库第三方的 PumpManual.md 文档为主线,结合仓库内实际的 pump.py 实现与真实.pump工程文件,完整讲解 Pump 的语法、构造、运行方式与底层原理,帮助你在自己的 C++ 项目中直接复用这套代码生成方案。
为什么要引入 Pump:模板与宏代码的重复性问题
模板库和宏库常常需要定义大量"仅在参数个数上有差异"的类、函数或宏。例如一个Foo模板要支持 0~3 个类型参数,就得手写四份几乎一样的声明;一旦参数上限提高到 10,工作量就变成十倍,而且每一份都容易敲错、漏改。这正是 GoogleTest 早期面临的真实困境:tuple、参数化测试、Mock 函数等机制都需要对 0~N 个参数逐一展开实现。
变长模板(variadic templates)和变长宏(variadic macros)理论上是解药,但在 Pump 诞生的年代,二者都尚未进入 C++ 标准,编译器支持也不广泛,可移植性差且能力有限。于是库作者们通常退而求其次,自己写脚本来生成代码;然而这类脚本往往把生成逻辑写死,与"最终代码的结构"脱节,难读难改——想给生成结果加一个小改动,可能要在脚本里做非常不直观的修改,尤其在做实验性调整时非常痛苦。
Pump 正是针对这一痛点给出的方案:把"生成逻辑"与"被生成的 C++ 代码"写在同一份文件里,让脚本结构天然反映输出结构。
Pump 是什么:一份文件,两种语言
Pump 是一个面向 C++ 的简单元编程工具。核心用法是:程序员编写一个foo.pump文件,文件内同时包含C++ 代码与操纵这份 C++ 代码的元代码。元代码支持:
- 对一段整数区间做迭代(
$for/$range); - 循环嵌套(一个
$for内再写$for); - 局部元变量定义(
$var); - 简单算术(表达式采用 Python 语法,如
0..n-1、k < n); - 条件分支(
$if/$elif/$else)。
可以把 Pump 看作一个极小的领域专用语言(DSL)。它被刻意设计得"非侵入"——元关键字以$开头、块边界用[[与]],不会干扰 Emacs 的 C++ 模式对代码的语法高亮与缩进——同时保持简洁,让 Pump 源码直观易维护。
核心特性一览
- 单一 Python 脚本实现,超强可移植性:整个工具就是一个 pump.py,无需构建、无需安装,跨平台直接运行;
- 智能排版:Pump 对生成代码中容易出现的超长行做了处理,遵循 Google 代码风格规范(Google style guide)在合理位置断行到 80 列以内,并正确缩进续行;
- 格式可读:相比 XML 方案更简洁、更接近自然代码;
- 与 Emacs 协作良好:格式对 Emacs 的 C++ 模式友好。
快速上手:安装、运行与输出
Pump 无需安装。直接使用 Python 2 解释器运行仓库内的脚本即可(该脚本为 Python 2 语法,且是 GoogleTest 早期版本):
python third_party/googletest/scripts/pump.py your_file.pump运行规则(对应 pump.py 中main的实现):
- 若传入的源文件以
.pump结尾,输出文件路径为去掉.pump后缀的同名文件,即foo.h.pump生成foo.h; - 生成的
.h文件头部会自动写入注释// This file was GENERATED by command: ...与// DO NOT EDIT BY HAND!!!,提示这是生成产物; - 若文件不以
.pump结尾,则把生成结果打印到标准输出。
值得注意的运行前提:pump.py的打印语句与异常捕获语法均为 Python 2 写法(如print '...'、except Exception, e),在 Python 3 环境下需要先用 2to3 等工具转换后才能执行。这是它在当前仓库中的历史版本形态,使用时请留意。
Pump 语言构造速查表
下表是 Pump 支持的全部元编程构造(完整继承自原文档):
| 构造 | 说明 |
|---|---|
$var id = exp | 定义一个具名常量值。$id在当前元词法块结束之前一直有效。 |
$range id exp..exp | 设置一个迭代变量的区间,该变量可在之后的多个循环中复用。 |
$for id sep [[ code ]] | 迭代。id的区间必须事先用$range定义。$id在code中有效。 |
$($) | 生成单个$字符。 |
$id | 具名常量或迭代变量的值。 |
$(exp) | 表达式的值。 |
$if exp [[ code ]] else_branch | 条件分支。 |
[[ code ]] | 元词法块(meta lexical block),块内的内容参与展开。 |
cpp_code | 原样输出的 C++ 代码。 |
$$ comment | 元注释,从$$到行尾结束,不参与生成。 |
关于换行的自由度:为了给开发者排版自由,Pump 会忽略紧跟$for foo之后、以及紧邻[[或]]的换行符。否则为了得到期望输出,你常常被迫写出很长的行。因此,如果希望输出中保留换行,有时需要在这些位置主动多插入一个空行。这条规则在实现上体现为词法规则(\[\[\n?)与(\]\]\n?)(见 pump.py 的TOKEN_TABLE),[[/]]后紧跟的换行会被一并吞掉。
第一个完整示例:从$var到多态模板
原文档给出的经典示例综合了元变量、区间、迭代与条件分支。下面这段 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; };逐行拆解这份输入:
$var n = 3定义元变量n,其值 3 会贯穿后续展开;$range i 0..n声明迭代变量i的区间为闭区间[0, 3];$for i [[ ... ]]是循环体,i每取一个值就展开一次块内代码;- 块内嵌套
$for j [[ , typename A$j]]:注意[[前的逗号是迭代分隔符,负责把多次迭代的结果用,连接起来,因此i=3时生成, typename A1, typename A2, typename A3; $if i == 0 ... $elif i <= 2 ... $else ...按i的值在blah a;、blah b;、blah c;三套类体中选择。
这里可以观察到 Pump 的一个关键设计:$range j 1..i出现在循环体内,其区间上限引用外层迭代变量i,即嵌套迭代可以依赖外层迭代的值——这正是模板元编程生成场景最需要的表达能力。
第二个示例:迭代分隔符的妙用
另一个示例展示迭代分隔符sep的作用。$for中位于id与[[之间的文本就是迭代分隔符,每次迭代之间输出一次:
$range i 1..n Func($for i + [[a$i]]); $$ The text between i and [[ is the separator between iterations.i的区间事先由$range i 1..n定义,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...可以看到:+作为分隔符把a1、a2、a3连接成a1 + a2 + a3。分隔符为空串时则各次迭代的输出直接首尾相连。在实现层面,分隔符逻辑位于 pump.py 的ForNode执行分支:循环体内每展开一次就追加一次代码,若当前不是最后一次迭代则再追加一次分隔符。
完整语法(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要点说明:
$var id = exp与$var id = [[ code ]]两种形式分别表示"把表达式求值结果存入变量"与"把一段元代码的展开结果存入变量",后者是强大的"代码片段复用"手段;exp是Python 语法的简单表达式,因此可以直接写i + 1、k < n、2 * j等算术与比较运算;- 顶层代码是无约束的
atomic_code*序列,普通 C++ 文本(cpp_code)原样透传,元指令则被逐条展开。
底层实现原理:token 化、AST 与求值
Pump 的实现虽然轻量(单文件约 850 行),但内部是一个完整的小型编译流水线。以 pump.py 源码为线索,其工作流程分为四步:
- 剥离元注释:
StripMetaComments(pump.py)先用正则^\s*\$\$.*\n删掉整行只有元注释的行,再用\s*\$\$.*删掉代码行尾的元注释; - 词法分析:
Tokenize与TokenizeLines(pump.py)依据TOKEN_TABLE中按优先级排列的正则,把源文本切成$var、$for、$range、$if、$id、$($)、[[、]]、code等 token。$id的正则是\$[_A-Za-z]\w*,而普通$落在最后兜底,保证表达式形态$(exp)能被单独识别; - 语法分析:
ParseToAST(pump.py)把 token 流递归下降解析为CodeNode/VarNode/RangeNode/ForNode/IfNode/RawCodeNode/ExpNode/LiteralDollarNode组成的 AST; - 解释执行:
RunCode/RunAtomicCode(pump.py)在Env环境中求值。Env(pump.py)用两个栈分别保存变量与区间,$for展开时把当前迭代值PushVariable进变量栈,循环体结束后再弹出,因此嵌套循环中内层可见外层变量、且互不污染。
表达式求值的实现很有意思:ParseExpNode用正则([_A-Za-z]\w*)把表达式里的每个标识符重写为self.GetValue("id")(pump.py),随后在Env.EvalExp里直接eval这段被改写过的 Python 表达式,从而实现"元表达式即 Python 表达式"。
输出美化:80 列自动断行
展开完成后,BeautifyCode(pump.py)逐行处理输出:超过 80 列的行,依据行内容分派到不同断行策略(pump.py):
- 单行注释
// ...过长 →WrapComment按单词拆分并在续行前补//; - 预处理指令过长 →
WrapPreprocessorDirective,用\续行符连接; - 普通代码过长 →
WrapPlainCode,优先在,、;处断行,其次在空格处断行,续行缩进 4 个空格; - 头文件保护宏(
#ifndef/#define/#endif //)、#include行与 IWYU pragma 属于风格规范的例外,保持单行不拆。
因此即使你的元代码产出超长行,最终.h文件也符合 Google 风格、可直接评审和提交。
仓库内的真实工程实例
原文档指出 Pump 的真实应用场景在 Google Test 与 Google Mock 的源码生成中:foo.h.pump生成foo.h。这一点在当前仓库中可以直接验证,.pump源文件与生成的头文件并存:
third_party/googletest/include/gtest/internal/gtest-tuple.h.pump:TR1 tuple 实现的生成源。文件开头$var n = 10定义了"最多支持 10 个 tuple 字段"的上限,随后用$range k 1..n、$range m 0..k-1等嵌套区间循环批量生成GTEST_0_TUPLE_到GTEST_10_TUPLE_的宏定义、make_tuple重载、tuple_size/tuple_element特化与get重载(见 gtest-tuple.h.pump)。其中还能看到$if k == 2 [[ ... ]]这类"只为特定参数个数生成 pair 特化"的精准条件展开;third_party/googletest/include/gtest/gtest-param-test.h.pump、third_party/googletest/include/gtest/internal/gtest-param-util-generated.h.pump、third_party/googletest/include/gtest/internal/gtest-type-util.h.pump:参数化测试与类型工具类的生成源;third_party/googlemock/include/gmock/gmock-generated-actions.h.pump、third_party/googlemock/include/gmock/gmock-generated-matchers.h.pump、third_party/googlemock/include/gmock/gmock-generated-function-mockers.h.pump、third_party/googlemock/include/gmock/gmock-generated-nice-strict.h.pump:Mock 动作、匹配器、函数 Mock 器与 Nice/Strict 包装类的生成源。
以gtest-tuple.h.pump为例,生成gtest-tuple.h后,展开的模板覆盖tuple<>到tuple<T0,...,T9>的全部 10 档,构造、拷贝、赋值、make_tuple、get、==/!=等一应俱全——这正是"一份 Pump 源文件代替上千行手写模板代码"的最佳写照。如果你计划在项目里引入 Pump,直接参考这些文件就是最好的上手教材。
实用技巧
- 变量紧跟字母或数字时的分隔:如果元变量后面紧跟字母或数字,用
[[]]插入空串来分隔。例如Foo$j[[]]Helper在j为 1 时生成Foo1Helper;不加[[]]时Foo$jHelper会被词法器把jHelper整体当作标识符求值,导致变量未定义的错误; - 避免过长的 Pump 源行:可以在任意位置用
[[]]加换行来断行。由于紧邻[[/]]的换行会被忽略,生成的代码中不会出现这个换行,既保持了源文件可读,又不会污染输出; - 用
$($)输出字面$:当需要生成包含$字符的代码(如 shell 脚本或某些宏)时使用; - 把嵌套迭代写成闭区间:
$range i 0..n是包含n的闭区间,且区间端点可以是表达式(如0..n-1),善用这一点可以精确控制展开次数,避免多生成一份或少生成一份。
小结
Pump 以一个不足千行的 Python 脚本,提供了"写一份、生成 N 份"的 C++ 元编程生产力:$var定义常量、$range声明区间、$for完成迭代、$if/$elif/$else实现条件、$($)转义字面量,配合[[]]词法块与自动 80 列排版,让模板库、宏库的批量生成既直观又可控。在当前仓库中,它不仅服务于 GoogleTest/Google Mock 自身的 tuple、参数化测试与 Mock 生成(见 third_party/googletest 与 third_party/googlemock 目录下的全部.pump文件),其"元代码与目标代码同文件混写"的思想也完全可以直接借鉴到任何需要批量产出重复 C++ 声明的项目里。唯一的注意点是仓库内 pump.py 为 Python 2 时代的实现,在 Python 3 环境中运行前需要先做语法转换。
- 人工智能
- AI Agent
- 多模态
- 语音
- AI 应用
【免费下载链接】ten-framework
Open-source framework for conversational voice AI agents
相关推荐
Pump 元编程工具完全指南:用 Python 脚本生成 C++ 模板代码(Apache Weex 仓库实战)
Pump 元编程工具完全指南:用 Python 脚本生成 C++ 模板代码(Apache Weex 仓库实战) Pump(Pump is Useful for
移动开发跨平台原生移动前端miniblink49 仓库内 Google Test Pump 元编程工具手册:用 `foo.pump` 批量生成 C++ 模板与宏样板代码
miniblink49 仓库内 Google Test Pump 元编程工具手册:用 foo.pump 批量生成 C++ 模板与宏样板代码 Pump(Pump
前端桌面应用Pump 元编程实战指南:用 .pump 脚本批量生成 C++ 模板样板代码
Pump 元编程实战指南:用 .pump 脚本批量生成 C++ 模板样板代码 Pump(Pretty Useful for Meta Programming)是
前端桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考