Chinaunix首页 | 论坛 | 博客
  • 博客访问: 236781
  • 博文数量: 52
  • 博客积分: 1355
  • 博客等级: 中尉
  • 技术积分: 485
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-06 12:23
文章分类

全部博文(52)

文章存档

2013年(5)

2012年(16)

2011年(26)

2010年(2)

2009年(1)

2008年(2)

我的朋友

分类: C/C++

2011-08-04 10:29:53

概要
    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中
代码: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";

       }

   }

} 


 

    输出:  
    1. -0.1-0.1-32-2.23232e+016

    解释:

    以上代码我相信比较难以理解的是 / 和 % 算符。

          / 符号我称之为“约束”或“动作”。它是在一个规则(Rule)匹配成功后执行的额外操作。这个额外的操作可能是:

    使用另一个Rule进行进一步的数据合法性检查。
    赋值(本例就是)。
    打印调试信息(正则表达式匹配比较难以跟踪,故此 Debug 能力也是 TPL 的一个关注点)。
    其他用户自定义动作。
    % 符号是列表算符(非常有用)。A % B 等价于 A (B A)* 这样的正则表达式。可匹配 ABABAB..A 这样的串。一个典型案例是用它匹配函数参数列表。

    样例二:识别以逗号分隔的浮点数并放入vector中
    代码:tpl/test/testtpl/SimpleGrammar.cpp
    1. // A simple grammar example.
    2. // What we use:
    3. // * Rules: /assign(), %, real(), gr(','), skipws()
    4. // * Matching: tpl::simple::match()
    5. void simple_grammar(){
    6.    std::vector<double> values; // you can change vector to other stl containers.
    7.    if (simple::match( " -.1 , -0.1 , +32. , -22323.2e+12 ", real()/assign(values) % gr(','), skipws()) ) {
    8.       for ( std::vector<double>::iterator it = values.begin(); it != values.end(); ++it) {
    9.          std::cout << *it << "\n";
    10.       }
    11.    }
    12. }

     输出:与样例一相同。

    解释:尽管看起来好像没有发生太大的变化。但是这两个样例本质上是不同的。主要体现在:

    正则表达式的类型不同。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 做下下面这个例子。

    样例三:运算器(Calculator)
    功能:可处理+-*/四则运算、()、函数调用(sin, cos, pow)。代码:tpl/test/testtpl/Calculator2.cpp (呵呵,只有60行代码哦!)
    1. #include <stack>
    2. #include <tpl/RegExp.h>
    3. #include <tpl/ext/Calculator.h>
    4. #include <cmath>
    5. using namespace tpl;
    6. void calculate2(){
    7.    typedef SimpleImplementation impl; // ---- define rules ----
    8.    impl::Allocator alloc;
    9.    std::stack<double> stk;
    10.    impl::Grammar::Var rFactor;
    11.    impl::Grammar rMul( alloc, '*' + rFactor/calc<std::multiplies>(stk) );
    12.     impl::Grammar rDiv( alloc, '/' + rFactor/calc<std::divides>(stk) );
    13.    impl::Grammar rTerm( alloc, rFactor + *(rMul | rDiv) );
    14.    impl::Grammar rAdd( alloc, '+' + rTerm/calc<std::plus>(stk) );
    15.    impl::Grammar rSub( alloc, '-' + rTerm/calc<std::minus>(stk) );
    16.    impl::Grammar rExpr( alloc, rTerm + *(rAdd | rSub) );
    17.    impl::Rule rFun( alloc, "sin"/calc(stk, sin) | "cos"/calc(stk, cos) | "pow"/calc(stk, pow) );
    18.    rFactor.assign( alloc, real()/assign(stk) | '-' + rFactor/calc<std::negate>(stk) | '(' + rExpr + ')' | (gr(c_symbol()) + '(' + rExpr % ',' + ')')/(gr(rFun) + '(') | '+' + rFactor );
    19. // ---- do match ----
    20.    for (;;) {
    21.       std::string strExp;
    22.       std::cout << "input an expression (q to quit): ";
    23.       if (!std::getline(std::cin, strExp) || strExp == "q") {
    24.          std::cout << '\n'; break;
    25.       }
    26.       try {
    27.          while ( !stk.empty() )
    28.             stk.pop();
    29.          if ( !impl::match(strExp.c_str(), rExpr + eos(), skipws()) )
    30.             std::cout << ">>> ERROR: invalid expression!\n";
    31.          else
    32.             std::cout << stk.top() << "\n";
    33.       } catch (const std::logic_error& e) {
    34.          std::cout << ">>> ERROR: " << e.what() << "\n";
    35.       }
    36.    }
    37. }
    38. // -------------------------------------------------------------------------

     解释:

    Grammar::Var 用于定义一个未赋值即被引用的Grammar。相应地,我们也有 Rule::Var。
    gr(Rule) 是将一个 Rule 转换为 Grammar。
    SimpleImplementation 是什么?嗯,这个下回聊。
    并不属于 tpl regex 库。代码也不多。参见:tpl/ext/Calculator.h
    阅读(900) | 评论(0) | 转发(0) |
    0

    上一篇:boost lamda用法(一)

    下一篇:TPL基础文法

    给主人留下些什么吧!~~