许式伟 ID:xushiweizh
418922次访问,排名112好友3人,关注者38
xushiweizh的文章
原创 125 篇
翻译 0 篇
转载 11 篇
评论 927 篇
许式伟的公告

本博客内容除非特殊说明均属原创,如需转载、引用其中的部分文字,请注意以下几点:

1)请在转载(引用)的内容开始添加本人署名,并提供本博客中相应文章的链接。如你的作品为非电子读物或纯文本,请给出链接的url。

2)请勿用于商业用途。

3)如果愿意,请给我邮件:xushiweizh@gmail.com,让我知道我的东西到哪去了。谢过。

重要链接


订阅

最近评论
yefeng_ok:非常期待C++0x标准的出台。
yefeng_ok:非常期待C++0x标准的出台。
LiYanRui:能不能把 free software 翻译为“自由软件”呢?

cairo 项目主页上很明确地说了:you should think of ``free'' as in ``free speech,'' not as in ``free beer.''
zhangyaoting196:

WWW.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!




ttkk1024:给老大顶一下
文章分类
收藏
相册
DocX预览图
Google vs. 百度
WinX相关
WINX团队
ebasil的专栏(RSS)
VisualFC/WINX专栏(RSS)
任风行(一路奔跑)(RSS)
绅士亦花心之WINX相关(RSS)
许伟群的专栏(RSS)
友情链接
QWL1996的专栏(RSS)
Sting的专栏(RSS)
SunHui的专栏(RSS)
不亦快斋(RSS)
于无声处(RSS)
手机开发论坛
珠穆朗玛(老汉)(RSS)
福&柯实验室(RSS)
存档
软件项目交易
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

原创 TPL: 一个新的正则表达式(regex)库收藏

新一篇: bcp: 给boost瘦身 | 旧一篇: 一个我不知道的C++语法

TPL: 一个新的正则表达式(regex)库

许式伟

2008-5-29


概要

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";
}
}
}

输出:

-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

// A simple grammar example.

// What we use:
// * Rules: /assign(), %, real(), gr(','), skipws()
// * Matching: tpl::simple::match()

void simple_grammar()
{
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()) )
{
for (
std::vector<double>::iterator it = values.begin();
it != values.end(); ++it)
{
std::cout << *it << "\n";
}
}
}

输出:与样例一相同。

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

  • 正则表达式的类型不同。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行代码哦!)

#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()) )
std::cout << ">>> ERROR: invalid expression!\n";
else
std::cout << stk.top() << "\n";
}
catch (const std::logic_error& e) {
std::cout << ">>> ERROR: " << e.what() << "\n";
}
}
}

// -------------------------------------------------------------------------

解释:

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

发表于 @ 2008年05月29日 01:23:00|评论(loading...)|编辑

新一篇: bcp: 给boost瘦身 | 旧一篇: 一个我不知道的C++语法

评论

#taodm 发表于2008-05-29 10:53:14  IP: 10.63.79.*
字符串格式正则的维护已经很困难了,表达式模板的正则则更更困难。
lex/yacc这样元编程,比模板元编程要更可取些。
#eXile_ 发表于2008-05-29 11:32:46  IP: 219.132.136.*
boost中的很多库编译起来都很慢,真是不愿意用它,c++0x不知道会不会有什么好的模块机制。
#xushiweizh 发表于2008-05-29 11:34:42  IP: 219.131.196.*
不知道你说的“维护”是指什么?喜欢lex/yacc,还是模板,个人认为仁者见仁的问题。
#xushiweizh 发表于2008-05-29 11:39:49  IP: 219.131.196.*
to eXile: 另外,其实tpl库这个是第2个版本了。第1个版本于2003年开发,被用于DocX这个工具,是动态正则表达式库。但是tpl v1写得很烂,我实在不便于维护下去了,这也直接导致DocX不再被更新。不过我相信等tpl v2完成后,DocX会被重写一个版本出来。
#taodm 发表于2008-05-29 14:18:54  IP: 10.63.79.*
用你的tpl写个能去除C++代码里//和/**/现成的注释的正则给我看看吧。
不要忘了"//"和'//'这种东西。
字符串型的类似于:
"//.*?(?:\n|\\z)|/\\*.*?\\*/" "|"
"\"(?:\\\\.|[^\\\\\"])*\"" "|"
"'\\\\?\"'"
#xushiweizh 发表于2008-05-29 21:54:37  IP: 125.89.30.*
如下:
impl::Rule::Var rCppEol;

rCppEol.assign( alloc,
find_set<'\r', '\n', '\\'>() +
(
'\\' + !eol() + rCppEol | eol()
));

char delim;
impl::Rule rString( alloc, ch('\'', '\"')/assign(delim) + *('\\' + ch_any() | ~ref(delim)) + ref(delim) );

impl::Rule rItem( alloc,
find_set<'/', '\'', '\"'>()/assign(result) +
(
"/*" + find<true>("*/") | /* I will be removed haha~ */
"//" + rCppEol | // Multiline \
comments are also allowed. haha~
('/' | rString)/assign(result)
));

impl::Rule( alloc, *rItem + done()/assign(result) );

完整代码参见:

http://winx.googlecode.com/svn/trunk/tpl/test/testtpl/RemoveCppComments.cpp
#xushiweizh 发表于2008-05-29 22:15:21  IP: 125.89.30.*
to taodm: 其实你问的问题是相当难的,C++简单一个//就有很大的复杂度,支持:

// comment1 \
continue to comments

我不知道为什么要这样的功能,给谁用的(没见过有人这样用)。

另外一个问题是,删除注释当然不能连带字符串中的/*字符也删除了,不得不又去识别字符串。
#aflyinghorse 发表于2008-05-29 23:11:42  IP: 123.113.116.*
我觉得spirit、xpressive编译速度还是可以接受的, 你的TPL的编译速度比spirit、xpressive快多少, 执行起来又快多少, 不知大侠有没有做过比较分析。
#taodm 发表于2008-05-30 09:07:52  IP: 10.63.79.*
所以,你再想想这句话吧:
字符串格式正则的维护已经很困难了,表达式模板的正则则更更困难。
#taodm 发表于2008-05-30 09:34:08  IP: 10.63.79.*
这个正则表达式,大部分源码统计程序都需要。
而且,目前很多以个人为单位制作的小源码统计工具都载在"//"和'//'这种东西上了。
关于//后面接受不接受转行符和/* */的*号前后是否接受转行符,正是它的可维护性受考验的根源。
#xushiweizh 发表于2008-05-30 09:37:51  IP: 219.131.196.*
不太了解你的因果关系。我只是说你的问题比较难而已。事实上,我认为用tpl表达的语义相当清晰的。而基于字符串的动态正则表达式,我只是看到一串无意义的符号。

另外,tpl修改一些细节:

1. 增加了Var<Type>,以避免使用ref关键字。这和Rule::Var, Grammar::Var功能类似。

2. grammar的匹配不再需要传入allocator。即
match(Source, Grammar, Skipper, Allocator) 改为:
match(Source, Grammar, Skipper)。

更新后的 RemoveCppCommets 仍然参见:

http://winx.googlecode.com/svn/trunk/tpl/test/testtpl/RemoveCppComments.cpp
#taodm 发表于2008-05-30 09:54:24  IP: 10.63.79.*
翻开mastering regex后,我发现我说的这个已经算很简单很简单的正则了。
#xushiweizh 发表于2008-05-30 13:48:44  IP: 219.131.196.*
这也正说明,这种正则的表示方式有问题,简单的已经这么难懂了。:D
#eXile_ 发表于2008-05-30 14:54:14  IP: 219.132.136.*
复杂的正则式理解起来都很困难,如果可能的话,采用正规文法要好理解得多。
#GeniusVczh 发表于2008-05-31 11:55:26  IP: 121.32.209.*
没有编译成机器码。我曾经用相同的表示方法做了一个跟yacc差不多的东西出来。

一般来说有个理由。因为real()/assign(values) % gr(','), skipws()可以变成如下:

X a=real();
X b=assign(values);
X c=gr(',');
X d=skipws();
a/b%c,d

等价的做法。所以不能被编译成机器码。因为优化也不会去做这种事情。当然,那个X是什么我不知道,不过总是有类型的。
#xushiweizh 发表于2008-06-01 10:48:49  IP: 222.129.116.*
X不是固定的类型。

real()的类型是Rule<RealG>
assign(values)的类型是Action<Assig<double> >
gr(',')的类型是Grammar<Gr<EqCh> >
...
所以,尽管real()/assign(values) % gr(',')等价于

Grammar a = real()/assign(values) % gr(',')

但是这里看似是一个普通赋值,单本质上相当于定义了一个函数,会损失一定的性能。
#xushiweizh 发表于2008-06-01 10:52:26  IP: 222.129.116.*
X不是固定的类型。

real()的类型是Rule<RealG>
assign(values)的类型是Action<Assig<double> >
gr(',')的类型是Grammar<Gr<EqCh> >
...
所以,尽管real()/assign(values) % gr(',')等价于

Grammar a = real()/assign(values) % gr(',')

但是这里看似是一个普通赋值,单本质上相当于定义了一个函数,会损失一定的性能。
#xushiweizh 发表于2008-06-01 11:29:22  IP: 222.129.116.*
X不是固定的类型。

real()的类型是Rule<RealG>
assign(values)的类型是Action<Assig<double> >
gr(',')的类型是Grammar<Gr<EqCh> >
...
所以,尽管real()/assign(values) % gr(',')等价于

Grammar a = real()/assign(values) % gr(',')

但是这里看似是一个普通赋值,单本质上相当于定义了一个函数,会损失一定的性能。
#suxiaojack 发表于2008-06-01 14:25:57  IP: 220.193.130.*
难以看懂啊!似乎写法比正则表达式串可以有更多变化。这是比较恐怖的事情。
#frontfast 发表于2008-06-02 00:01:23  IP: 118.24.211.*
这个有些难度,顶一下先



SIGNATURE---------------------

学习、讨论C++
QQ群:25119736
#h_falls 发表于2008-06-02 07:17:09  IP: 127.0.0.*
看来还是perl 的 reg 简洁快速
#NewVC1978 发表于2008-06-02 07:21:49  IP: 218.81.193.*
多谢了,c++ reg原来是如此好用!
#WhatX 发表于2008-06-02 09:30:09  IP: 221.221.11.*
似乎不是标准正则表达式的语法。
发表评论  


当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
Csdn Blog version 3.1a
Copyright © 许式伟