TPL: 一個新的正則表達式(regex)庫

  

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()
{
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";
}
}
}

輸出:與樣例一相同。

解釋:儘管看起來好像沒有發生太大的變化。但是這兩個樣例本質上是不同的。主要體現在:

  • 正則表達式的類型不同。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, Allocator)。

第二個例子如果用 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(), 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";
}
}
}

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

解釋:

  • Grammar::Var 用於定義一個未賦值即被引用的Grammar。相應地,我們也有 Rule::Var。
  • gr(Rule) 是將一個 Rule 轉換爲 Grammar。
  • SimpleImplementation 是什麼?嗯,這個下回聊。
  • <tpl/ext/Calculator.h> 並不屬於 tpl regex 庫。代碼也不多。參見:tpl/ext/Calculator.h
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章