C++ 中正则表达式(regex)库已经很多。光 boost 中就有3个:regex、spirit、xpressive。那么我们为什么还需要一个新的呢?
多数正则表达式库都需要一个编译(compile)过程。即:通过解释一个正则表达式的字符串(pattern)来生成该正则表达式的内部表示(字节码)。例如 boost regex 就是这样。这类我们称之为动态正则表达式库。
spirit、xpressive 例外。他们直接通过重载 C++ 的操作符来表达一个正则表达式。在你用C++语法描述完一个正则表达式,它已经是内部表示(被C++编译器编译成了机器码)。这一类我们称之为静态正则表达式库。
静态正则表达式库的好处主要有二:
缺点:
TPL 属于静态正则表达式库。本文也不准备讨论动态正则表达式。需要指出,xpressive 既支持动态正则表达式,也支持静态的正则表达式,但是我们并不考虑其动态正则表达式部分。
TPL 全称为 Text Processing Library(文本处理库)。spirit、xpressive 是很好的东西,实现 TPL 库中对这两者有所借鉴。
说起来开发 TPL 库的理由看起来挺好笑的:原因是 spirit、xpressive 太慢。不是执行慢,而是编译慢。我的机器算起来也不算差,但是每次修改一点点代码,编译过程都等待半天,实在受不了这样的开发效率。
从机理上讲,TPL 并无特别让人振奋之处。该有的 spirit、xpressive 相信都有了。三者都基于“表达式模板(Expression Templates)” 这样的技术。
闲话少说,这里给几个实际的样例让大家感受下:
代码:tpl/test/testtpl/Simplest.cpp
- #include <vector>
- #include <tpl/RegExp.h>
- using namespace tpl;
- // What we use:
- // * Rules: /assign(), %, real(), ws()
- // * Matching: tpl::simple::match()
- void simplest()
- {
- std::vector<double> values; // you can change vector to other stl containers.
- if ( simple::match(
- "-.1 -0.1 +32. -22323.2e+12",
- real()/assign(values) % ws()) )
- {
- for (
- std::vector<double>::iterator it = values.begin();
- it != values.end(); ++it)
- {
- std::cout << *it << "\n";
- }
- }
- }
输出:
-0.1 -0.1 -32 -2.23232e+016 |
解释:
以上代码我相信比较难以理解的是 / 和 % 算符。
/ 符号我称之为“约束”或“动作”。它是在一个规则(Rule)匹配成功后执行的额外操作。这个额外的操作可能是:
% 符号是列表算符(非常有用)。A % B 等价于 A (B A)* 这样的正则表达式。可匹配 ABABAB..A 这样的串。一个典型案例是用它匹配函数参数列表。
代码:tpl/test/testtpl/SimpleGrammar.cpp
- // A simple grammar example.
- // What we use:
- // * Rules: /assign(), %, real(), gr(','), skipws()
- // * Matching: tpl::simple::match()
- void simple_grammar()
- {
- simple::Allocator alloc;
- std::vector<double> values; // you can change vector to other stl containers.
- if ( simple::match(
- " -.1 , -0.1 , +32. , -22323.2e+12 ",
- real()/assign(values) % gr(','), skipws(), alloc) )
- {
- for (
- std::vector<double>::iterator it = values.begin();
- it != values.end(); ++it)
- {
- std::cout << *it << "\n";
- }
- }
- }
输出:与样例一相同。
解释:尽管看起来好像没有发生太大的变化。但是这两个样例本质上是不同的。主要体现在:
第二个例子如果用 Rule 而不是用 Grammar 写,看起来是这样的:
- if ( simple::match(
- " -.1 , -0.1 , +32. , -22323.2e+12 ",
- (skipws() + real()/assign(values)) % (skipws() + ',')) ) ...
你可能认为这并不复杂。单对这个例子而言,确实看起来如此。但是如果你这样想,不妨用 Rule 做下下面这个例子。
功能:可处理+-*/四则运算、()、函数调用(sin, cos, pow)。代码:tpl/test/testtpl/Calculator2.cpp(呵呵,只有60行代码哦!)
- #include <stack>
- #include <tpl/RegExp.h>
- #include <tpl/ext/Calculator.h>
- #include <cmath>
- using namespace tpl;
- void calculate2()
- {
- typedef SimpleImplementation impl;
- // ---- define rules ----
- impl::Allocator alloc;
- std::stack<double> stk;
- impl::Grammar::Var rFactor;
- impl::Grammar rMul( alloc, '*' + rFactor/calc<std::multiplies>(stk) );
- impl::Grammar rDiv( alloc, '/' + rFactor/calc<std::divides>(stk) );
- impl::Grammar rTerm( alloc, rFactor + *(rMul | rDiv) );
- impl::Grammar rAdd( alloc, '+' + rTerm/calc<std::plus>(stk) );
- impl::Grammar rSub( alloc, '-' + rTerm/calc<std::minus>(stk) );
- impl::Grammar rExpr( alloc, rTerm + *(rAdd | rSub) );
- impl::Rule rFun( alloc,
- "sin"/calc(stk, sin) | "cos"/calc(stk, cos) | "pow"/calc(stk, pow) );
- rFactor.assign( alloc,
- real()/assign(stk) |
- '-' + rFactor/calc<std::negate>(stk) |
- '(' + rExpr + ')' |
- (gr(c_symbol()) + '(' + rExpr % ',' + ')')/(gr(rFun) + '(') |
- '+' + rFactor );
- // ---- do match ----
- for (;;)
- {
- std::string strExp;
- std::cout << "input an expression (q to quit): ";
- if (!std::getline(std::cin, strExp) || strExp == "q") {
- std::cout << '\n';
- break;
- }
- try {
- while ( !stk.empty() )
- stk.pop();
- if ( !impl::match(strExp.c_str(), rExpr + eos(), skipws(), alloc) )
- std::cout << ">>> ERROR: invalid expression!\n";
- else
- std::cout << stk.top() << "\n";
- }
- catch (const std::logic_error& e) {
- std::cout << ">>> ERROR: " << e.what() << "\n";
- }
- }
- }
- // -------------------------------------------------------------------------
解释: