最近為了解析SQL語法,懷著試一試的心態(tài)去翻了翻boost的spirit庫,因為該庫的文檔的簡介里寫著LL parser framework represents parsers directly as EBNF grammars in inlined C++。看著framework這個詞自然覺得這個庫很牛B,試用了一下果然如此。
所謂EBNF即擴展巴克斯范式,是一種描述Context-Free Language的文法。在目前常見的非自然語言中,大部分都可以用EBNF表示。例如:
group ::='('exp ')'
factor ::=integer| group
term ::=factor(('*'factor)|('/'factor ))*
exp ::=term(('+'term)|('-'term ))*
這是一個整數(shù)表達式的EBNF。該段描述用spirit在C++中的實現(xiàn)則是:
rule<> group, factor, term, exp;
group = '(' >> exp >> ')';
factor = int_p | group;
term = factor >> *(('*' >> factor) | ('/' >> factor));
exp = term >> *(('+' >> term) | ('-' >> term));
這里使用=代替::=, 用>>代替空格連接。并且由于C++語法所限,EBNF中后置的*在spirit中改為前置。
等式左邊的單詞被稱為一個rule,等式右邊為rule的定義。我們可以看出一個group是一個exp加上一對括號,一個factor是一個整數(shù)或者一個group,一個term是一個或多個factor用*/連接,一個exp是一個或多個term用+-連接。處于最頂端的exp可以據(jù)此識別出以下表達式
12345
-12345
+12345
1 + 2
1 * 2
1/2 + 3/4
1 + 2 + 3 + 4
1 * 2 * 3 * 4
(1 + 2) * (3 + 4)
(-1 + 2) * (3 + -4)
1 + ((6 * 200) - 20) / 6
(1 + (2 + (3 + (4 + 5))))
得到一個rule之后,我們就可以用 parse函數(shù)對一個串進行識別了。例如
parse( " (1 + (2 + (3 + (4 + 5)))) " , exp);
該函數(shù)返回一個結構parse_info,可以通過訪問其中的full成員來判斷是否成功識別,也可以訪問stop成員來獲知失敗的位置。這里要特別提一點,關于各個符號之間的空格,spirit的文檔的正文說的是給parse再傳一個參數(shù)space_p,通知parse跳過所有的空格,然而在FAQ中又提到,如果使用以上方法定義rule,第三個參數(shù)傳space_p會失敗。原因是使用rule默認定義的規(guī)則被稱為character level parsing,即字符級別解析,而parse的第3個參數(shù)僅適用于phrase level parsing,即語法級別解析。要使用第3個參數(shù)可以有幾種方法。
1。在parse的第二個參數(shù)直接傳入一個EBNF表達式,不創(chuàng)建rule對象。
parse( " hello world " , * anychar_p, space_p);
2。以rule<phrase_scanner_t>創(chuàng)建rule。
rule < phrase_scanner_t > exp;
注意雖然可以用這兩個辦法屏蔽空格,但是這樣可能完全改變EBNF文法的語義,尤其是在語言本身需要識別空格的時候。對于這種情況,可以不使用第三個參數(shù),并在需要出現(xiàn)空格的地方加上space_p,或者+space_p及*space_p,其中+和*分別表示后面的符號連續(xù)出現(xiàn)一次以上和0次以上。例如一個以空格分隔的整數(shù)列表可以寫成int_p >> *(+space_p >> int_p)
如上使用parse可以識別一個串,但并不能做更多的操作,例如將語法里的各個成分提取出來。對于這樣的需求,可以通過actor實現(xiàn)。下面是使用actor的一個簡單例子
bool
parse_numbers(char const* str, vector<double>& v)

{
return parse(str,

// Begin grammar
(
real_p[push_back_a(v)] >> *(',' >> real_p[push_back_a(v)])
)
,
// End grammar
space_p).full;
}
注意到real_p后面的[],中括號里面是一個仿函數(shù)(函數(shù)指針或者函數(shù)對象),該仿函數(shù)具有如下調(diào)用型別
void operator()(IterT first, IterT last) const;
void operator()(NumT val) const;
void operator()(CharT ch) const;
一旦spase發(fā)現(xiàn)了匹配real_p的子串,就會調(diào)用該functor。不同的rule可能會對應不同的調(diào)用型別。
第一個型別針對一般規(guī)則,first和last為兩個指向字符的迭代器(一般為char*),匹配的子串為[first, last)
第二個型別針對數(shù)字型規(guī)則,如real_p和int_p, 參數(shù)val是一個數(shù)字類型。
第三個性別針對單字符型規(guī)則,如space_p, 參數(shù)ch是一個字符類型。
real_p[push_back_a(v)]中的push_back_a是一個spirit已經(jīng)定義好的functor,它會將匹配好的內(nèi)容依照匹配到的時間順序調(diào)用v的push_back函數(shù)加入到v中。
到此spirit的常用功能就都介紹完了。要詳細深入了解可以參考spirit的文檔。
最后在題一個注意要點。spirit的各種EBNF連接都是指針連接,因此才能在expression被賦值前就在group的定義里面使用。所以在使用EBNF的時候一定要小心不要將局部變量的rule提供給全局或者類成員變量使用,例如:
class A

{
rule<> s;
A()

{
rule<> r = int_p | hex_p;

s = r >> *(+space_p >> r); //error, r destructed after return
}
};

如果真想使用局部作用域,可以在局部的rule前面加上static.
posted on 2005-12-18 12:02
shifan3 閱讀(7121)
評論(5) 編輯 收藏 引用 所屬分類:
template 、
Boost 、
C++