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 太慢。不是執行慢,而是編譯慢。我的機器算起來也不算差,但是每次修改一點點代碼,編譯過程都等待半天,實在受不了這樣的開發效率。
閑話少說,這裡給幾個實際的樣例讓大家感受下:
輸出:
解釋:
以上代碼我相信比較難以了解的是 / 和 % 算符。
/ 符号我稱之為“限制”或“動作”。它是在一個規則(Rule)比對成功後執行的額外操作。這個額外的操作可能是:
使用另一個Rule進行進一步的資料合法性檢查。
指派(本例就是)。
列印調試資訊(正規表達式比對比較難以跟蹤,故此 Debug 能力也是 TPL 的一個關注點)。
其他使用者自定義動作。
% 符号是清單算符(非常有用)。A % B 等價于 A (B A)* 這樣的正規表達式。可比對 ABABAB..A 這樣的串。一個典型案例是用它比對函數參數清單。
輸出:與樣例一相同。
解釋:盡管看起來好像沒有發生太大的變化。但是這兩個樣例本質上是不同的。主要展現在:
正規表達式的類型不同。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 寫,看起來是這樣的:
你可能認為這并不複雜。單對這個例子而言,确實看起來如此。但是如果你這樣想,不妨用 Rule 做下下面這個例子。
Grammar::Var 用于定義一個未指派即被引用的Grammar。相應地,我們也有 Rule::Var。
gr(Rule) 是将一個 Rule 轉換為 Grammar。
SimpleImplementation 是什麼?嗯,這個下回聊。
目前 tpl/RegExp.h (正則庫)相關的樣例有:
simplest: 最簡單TPL樣例,類似Hello, world!
grammar: 還是簡單樣例,稍微加了點複雜性。
urlparams: 用TPL分析url參數。即prop1=val1&prop2=val2&prop3=val3…
calculator: 用TPL實作一個電腦。支援+-*/、()、sin/cos/pow/max
removecomments: 删除C++代碼中的注釋。
removecomments2: 還是C++代碼中的注釋。但是使用了tpl/c/Lex.h擴充子產品。
includefiles: 提取C++源檔案中的include檔案清單。可改善下做代碼依賴關系的定性分析。
目前 tpl/Emulator.h (虛拟機)相關的樣例有:
emulator: 示範我們的虛拟機彙編指令。
variant: 示範虛拟機的類型系統。