C++ 中正则表达式(regex)库已经很多。光 boost 中就有3个:regex、spirit、xpressive.那么我们为什么还需要一个新的呢?
多数正则表达式库都需要一个编译(compile)过程。即:通过解释一个正则表达式的字符串(pattern)来生成该正则表达式的内部表示(字节码)。例如 boost regex 就是这样。这类我们称之为动态正则表达式库。
spirit、xpressive 例外。他们直接通过重载 C++ 的操作符来表达一个正则表达式。在你用C++语法描述完一个正则表达式,它已经是内部表示(被C++编译器编译成了机器码)。这一类我们称之为静态正则表达式库。
静态正则表达式库的好处主要有二:
性能好。由于匹配代码直接编译成为了机器码,故此通常性能会好过动态的正则表达式。
与 C++ 语言可形成良好的互动。可以非常容易在正则表达式中获得执行C++代码的时机。
缺点:
正则表达式必须在编译期确定。如果你希望用户可以输入一个正则表达式,那么静态正则表达式库不能直接满足你的需求。
TPL 属于静态正则表达式库。本文也不准备讨论动态正则表达式。需要指出,xpressive 既支持动态正则表达式,也支持静态的正则表达式,但是我们并不考虑其动态正则表达式部分。
TPL 全称为 Text Processing Library(文本处理库)。spirit、xpressive 是很好的东西,实现 TPL 库中对这两者有所借鉴。
说起来开发 TPL 库的理由看起来挺好笑的:原因是 spirit、xpressive 太慢。不是执行慢,而是编译慢。我的机器算起来也不算差,但是每次修改一点点代码,编译过程都等待半天,实在受不了这样的开发效率。
从机理上讲,TPL 并无特别让人振奋之处。该有的 spirit、xpressive 相信都有了。三者都基于“表达式模板(Expression Templates)” 这样的技术。
闲话少说,这里给几个实际的样例让大家感受下:
样例一:识别以空格分隔的浮点数并放入vector中代码:
#include <vector> // What we use: void simplest() |
输出:
-0.1
-0.1
-32
-2.23232e+016
解释:
以上代码我相信比较难以理解的是 / 和 % 算符。
/ 符号我称之为“约束”或“动作”。它是在一个规则(Rule)匹配成功后执行的额外操作。这个额外的操作可能是:
使用另一个Rule进行进一步的数据合法性检查。
赋值(本例就是)。
打印调试信息(正则表达式匹配比较难以跟踪,故此 Debug 能力也是 TPL 的一个关注点)。
其他用户自定义动作。
% 符号是列表算符(非常有用)。A % B 等价于 A (B A)* 这样的正则表达式。可匹配 ABABAB……A 这样的串。一个典型案例是用它匹配函数参数列表。
样例二:识别以逗号分隔的浮点数并放入vector中代码:
// A simple grammar example. void simple_grammar() if ( simple::match( |
输出:与样例一相同。
解释:尽管看起来好像没有发生太大的变化。但是这两个样例本质上是不同的。主要体现在:
正则表达式的类型不同。real()/assign(values) % ws() 是一个Rule.而 real()/assign(values) % gr(',') 是一个 Grammar.简单来说,Rule 可以认为是词法级别的东西。Grammar 是语法级别的东西。Grammar 的特点在于,它匹配一个语法单元前,总会先调用一个名为Skipper的特殊Rule.上例中 Skipper 为 skipws()。
两个 match 的原型不同。第一个match的原型是:match(Source, Rule), 第二个match的原型是:match(Source, Grammar, Skipper)。
第二个例子如果用 Rule 而不是用 Grammar 写,看起来是这样的:
if ( simple::match( " -.1 , -0.1 , +32. , -22323.2e+12 ", (skipws() + real()/assign(values)) % (skipws() + ',')) ) ... |
你可能认为这并不复杂。单对这个例子而言,确实看起来如此。但是如果你这样想,不妨用 Rule 做下下面这个例子。