版本1.6.x以前的spirit能支持大部分的編譯器。在1.8.0以後,因爲spirit加入了不少C++的新特性,使兼容各類不標準的編譯器的工做變得很是鬱悶,因而Spirit再也不支持不標準的C++編譯器,這意味着VC7.1,BCB2006以及GCC3.1以前版本將再也不被支持。(注:聽說江湖上有新版Spirit的牛人修改版,能夠工做在VC6和VC7上,具體狀況不明) 正則表達式
real = ["+"|"-"], digit, [{digit}], [".", digit, [{digit}]], ["E"|"e", ["+"|"-"], digit, {digit}]
有了上面的知識,咱們能夠試試解析以逗號分隔的實數序列
字符串形式爲"real,real,real,...real"
參考上面的一堆預置解析器,咱們能夠這樣組合:
- real_p >> *(',' >> real_p);
更簡單點,咱們可使用%操做符
因而很簡單地寫下這樣的代碼:
- {
-
- const char *szNumberList = "12.4,1000,-1928,33,30";
- parse_info<> r = parse( szNumberList, real_p % ',' );
- cout << "parsed " << (r.full?"successful":"failed") << endl;
- cout << szNumberList << endl;
-
- cout << string(r.stop - szNumberList, ' ') << '^' << endl;
- }
解析成功!接下來咱們就把裏面的數字取出來,解析器重載了[]操做符,在這裏能夠放入
函數或函數對象,放在這裏面的函數或函數對象在Spirit裏稱之爲
Actor
對於real_p,它要求形式爲:void func(double v)的
函數或函數對象,下面咱們就來取出這些數字:
- #include <iostream>
- #include <boost/spirit.hpp>
- using namespace std;
- using namespace boost::spirit;
- void showreal(double v)
- {
- cout << v << endl;
- }
- int main()
- {
-
- const char *szNumberList = "12.4,1000,-1928,33,30";
-
- parse_info<> r = parse( szNumberList, real_p[&showreal] % ',' );
- cout << "parsed " << (r.full?"successful":"failed") << endl;
- cout << szNumberList << endl;
-
- cout << string(r.stop - szNumberList, ' ') << '^' << endl;
- return 0;
- }
再次運行,顯示了一列數字了吧:)
再寫一個函數對象版本的,此次把這列數字寫到
vector裏
- #include <iostream>
- #include <vector>
- #include <boost/spirit.hpp>
- using namespace std;
- using namespace boost::spirit;
- int main()
- {
-
- struct pushreal
- {
- void operator()(double v) const
- {
- m_vec.push_back(v);
- }
- pushreal(vector<double> &vec)
- :m_vec(vec){;}
- private:
- vector<double> &m_vec;
- };
- vector<double> reallist;
-
- const char *szNumberList = "12.4,1000,-1928,33,30";
-
- parse_info<> r = parse( szNumberList, real_p[pushreal(reallist)] % ',' );
- cout << "parsed " << (r.full?"successful":"failed") << endl;
- cout << szNumberList << endl;
-
- cout << string(r.stop - szNumberList, ' ') << '^' << endl;
-
- copy(reallist.begin(),reallist.end(),ostream_iterator<double>(cout," "));
- return 0;
- }
我不得不告訴你,
Spirit也提供了比偶的這個pushreal強得多的函數對象
push_back_a(須要冷靜哈)
- #include <iostream>
- #include <vector>
- #include <boost/spirit.hpp>
- using namespace std;
- using namespace boost::spirit;
- int main()
- {
- vector<double> reallist;
-
- const char *szNumberList = "12.4,1000,-1928,33,30";
-
- parse_info<> r = parse( szNumberList, real_p[push_back_a(reallist)] % ',' );
- cout << "parsed " << (r.full?"successful":"failed") << endl;
- cout << szNumberList << endl;
-
- cout << string(r.stop - szNumberList, ' ') << '^' << endl;
-
- copy(reallist.begin(),reallist.end(),ostream_iterator<double>(cout," "));
- return 0;
- }
在上面的實數序列中,若是中間含有空格或TAB,這個解析就不能成功,這時可使用
parse函數的另外一個重載版本:
- parse_info<charT const*> parse(字符串, 解析器1, 解析器2);
- parse_info parse(IteratorT first, IteratorT last, 解析器1, 解析器2);
其中的解析器2用於跳過其匹配的字符,咱們要跳過空格,因此解析器2可使用
space_p:
- parse_info<> r = parse( szNumberList,
- real_p[push_back_a(reallist)] % ',',
- space_p);
若是更進一步,咱們甚至能夠連逗號也跳過,直接取得一列數字:
- parse_info<> r = parse( szNumberList,
- *real_p[push_back_a(reallist)],
- space_p|ch_p(','));
除
push_back_a外,
Spirit還提供了很多有用的
Actor(就是函數對象啦),以下
注:這裏的
ref是外部數據,就象上例中的reallist,
value_ref是外部數值,
value是解析出的數值
- increment_a(ref) 自增 ++ref
- decrement_a(ref) 自減 --ref
賦值操做
- assign_a(ref) 賦值 ref = value
- assign_a(ref, value_ref) 常量賦值 ref = value_ref
容器操做
- push_back_a(ref) ref.push_back(value)
- push_back_a(ref, value_ref) ref.push_back(value_ref)
- push_front_a(ref) ref.push_front(value)
- push_front_a(ref, value_ref) ref.push_front(value_ref)
- clear_a(ref) ref.clear()
關聯容器操做(vt類型是typeof(ref)::value_type)
- insert_key_a(ref, value_ref) ref.insert(vt(value, value_ref))
- insert_at_a(ref, key_ref_, value_ref) ref.insert(vt(key_ref,value_ref))
- insert_at_a(ref, key_ref) ref.insert(vt(key_ref,value))
- assign_key_a(ref, value_ref) ref[value] = value_ref
- erase_a(ref) ref.erase(ref,value)
- erase_a(ref, key_ref) ref.erase(ref,key_ref)
其它操做
- swap_a(aref, bref) 交換aref和bref
例三,四則運算
若是說上面的兩個例子用正則表達式也能輕鬆搞定了話,那麼接下來你就能體會到Spirit的強大威力!
解析四則運算表達式,一樣先要把EBNF規則寫出來:
- //實數或者是括號包圍的子表達式
- 因子 = 實數 | '(' , 表達式 , ')';
- //因子*因子或因子/因子,可連續乘除也可只是一個因子
- 乘除計算 = 因子,{('*',因子)|('/',因子)};
- //加減計算,與上面相似
- 表達式 = 乘除計算,{('+',乘除計算)|('-',乘除計算)};
這個定義已經隱含了優先級:
- 要計算表達式(加減計算),必然要先計算乘除計算;
- 要計算乘除計算,就要先計算因子;
- 要計算因子,要麼獲得一個數字,要麼就要計算括號內的子表達式。
轉成Spirit解析器組合:
- rule<phrase_scanner_t> factor, term, exp;
- factor = real_p | ('(' >> exp >> ')');
- term = factor >> *(('*' >> factor) | ('/' >> factor));
- exp = term >> *(('+' >> term) | ('-' >> term));
這裏的rule是一個規則類,它能夠做爲全部解析器的佔位符,定義以下:
- template<
- typename ScannerT = scanner<>,
- typename ContextT = parser_context<>,
- typename TagT = parser_address_tag>
- class rule;
其中的模板參數做用是:
ScannerT 掃描器策略類
它有兩類工做模式,一種是字符模式,一種是語法模式,默認的scanner<>是工做於字符模式的。
ContextT 內容策略類
它決定了rule裏的成員變量以及Actor的類型,稍後會有利用這個模板參數來加入自定義的成員變量的例子
TagT 標識策略類
每一個rule都有一個id()方法,用於識別不一樣的rule,TagT就用於決定id()返回的數據(後面會講到)。
這三個策略類能夠不按順序地輸入,如
- rule<parser_address_tag,parser_context<>,scanner<> >;
- rule<parser_context<> >;
- rule<scanner<>,parser_address_tag >;
是同一個類。
值得注意的是ScannerT,咱們上面沒有使用默認的scanner<>,而是使用了phrase_scanner_t,由於工做於字符模式的掃描器沒法與parse的解析器2參數(跳過匹配字符,見上)一同工做,這樣就沒法解析含有空格的表達式,這可不完美,因此咱們使用的工做於語法模式的phrase_scanner_t。
- #include <iostream>
- #include <vector>
- #include <boost/spirit.hpp>
- using namespace std;
- using namespace boost::spirit;
- int main()
- {
- rule<phrase_scanner_t> factor, term, exp;
- factor = real_p | ('(' >> exp >> ')');
- term = factor >> *(('*' >> factor) | ('/' >> factor));
- exp = term >> *(('+' >> term) | ('-' >> term));
- const char *szExp = "1 + (2 * (3 / (4 + 5)))";
- parse_info<> r = parse( szExp , exp, space_p);
- cout << "parsed " << (r.full?"successful":"failed") << endl;
- return 0;
- }
接下來,要獲得這個四則表達式的計算結果,這纔是咱們要的,因而Spirit自帶的lambda支持:phoenix登場!
頭文件:
#include <boost/spirit/phoenix.hpp>
phoenix提供和與Boost.Lambda相似的功能,它能夠直接就地生成匿名函數對象,phoenix使用arg1,arg2,arg3...做爲佔位符,Boost.Lambda則使用_1,_2,_3...,使用舉例:
- #include <iostream>
- #include <vector>
- #include <boost/spirit.hpp>
- #include <boost/spirit/phoenix.hpp>
- using namespace std;
- using namespace boost::spirit;
- using namespace phoenix;
- int main()
- {
- vector<int> vec(10);
- int i=0;
- for_each(vec.begin(),vec.end(),arg1 = var(i)++);
- for_each(vec.begin(),vec.end(),cout << arg1 << endl);
- return 0;
- }
這樣咱們就能夠利用phoenix提供的匿名函數對象做爲Actor, 同時利用Spirit提供的closure類爲rule添加一個val成員變量存儲計算結果(還記得rule的ContextT策略嗎?)
- #include <iostream>
- #include <vector>
- #include <boost/spirit.hpp>
- #include <boost/spirit/phoenix.hpp>
- using namespace std;
- using namespace boost::spirit;
- using namespace phoenix;
- int main()
- {
- struct calc_closure : boost::spirit::closure<calc_closure, double>
- {
- member1 val;
- };
- rule<phrase_scanner_t, calc_closure::context_t> factor, term, exp;
- factor = real_p[factor.val = arg1] | ('(' >> exp[factor.val = arg1] >> ')');
- term = factor[term.val = arg1] >> *(('*' >> factor[term.val *= arg1]) | ('/' >> factor[term.val /= arg1]));
- exp = term[exp.val = arg1] >> *(('+' >> term[exp.val += arg1]) | ('-' >> term[exp.val -= arg1]));
- const char *szExp = "1 + (2 * (3 / (4 + 5)))";
- double result;
- parse_info<> r = parse( szExp , exp[assign_a(result)], space_p);
- cout << szExp;
- if(r.full)
- {
- cout << " = " << result << endl;
- }
- else
- {
- cout << endl << string(r.stop - szExp, ' ') << '^' << endl;
- }
- return 0;
- }
感到很神奇?這裏有必要多說一下boost::spirit::closure的做用,它的使用方法是:
- struct name : spirit::closure<name, type1, type2, type3,... typen>
- {
- member1 m_name1;
- member2 m_name2;
- member3 m_name3;
- ...
- memberN m_nameN;
- };
一種類型對應一個member,使用name::context_t做爲ContextT策略的rule就會含有N個相應的變量,並且這個rule的Actor將會接收到member1對應的數據。
也能夠用於語法類,如grammar<t, name::context_t="">,關於語法類,後面章節將會提到。
注:默認最多到member3,要想使用更多數據,在包含Spirit頭文件前預約義PHOENIX_LIMIT和BOOST_SPIRIT_CLOSURE_LIMIT,如
- #define PHOENIX_LIMIT 10
- #define BOOST_SPIRIT_CLOSURE_LIMIT 10
提升 例四,使用自定義語法類框架
在
例三中,咱們用了不足50行的代碼搞定了一個四則運算字符串的解析,可見Spirit的威力巨大。不過僅僅這樣還不夠,Spirit但是號稱輕量語法解析庫,一個基本可用的腳本可不止四則運算那麼點東西,起碼要有賦值、條件、循環、輸入輸出吧?
有了上面的知識,再加上一些編程經驗,一個個搞定它們應該不是太難的事,但把全部的規則堆在一塊兒不只噁心,並且難以維護,因而Spirit提供了語法類
grammar來集中管理。
grammar的定義以下:
- template<
- typename DerivedT,
- typename ContextT = parser_context<> >
- struct grammar;
DerivedT參數是反引用自身類型,若是用過
WTL庫的可能對這個比較熟悉,使用這種技術能夠保持多態性的同時消除虛函數帶來的性能開稍。
ContextT參數就是
內容策略類,在
例三中提到過。
編寫一個
語法類框架的基本形式以下:
- struct my_grammar : public grammar<my_grammar>
- {
- template <typename ScannerT>
- struct definition
- {
- rule r;
- definition(my_grammar const& self) { r =
- rule const& start() const { return r; }
- };
- };
它繼承自
grammar,模板參數DerivedT就是自身類型,ContextT可使用默認的parser_context<>或者本身定義一個(好比
例三中的
closure)。
這個類內部必需要有一個
definition類的定義,這個
definition類的模板參數ScannerT由框架使用環境決定。它由兩個重要方法:
- start() const函數:它返回一個rule。使用my_grammar解析時,就從這個rule開始。
- definition構造函數:這裏是初始化rule的最好場所。它的self參數是整個my_grammar的實例引用,接下去你會發現這但是個頗有用的東西。
同時,很重要的一點:
語法類自己也是一個
解析器,它也能與其它
解析器組合。
下面,咱們把例三中的四則運算解析功能放到一個語法類中,而後再用這個語法類與其它解析器合做弄一個簡單的賦值操做出來:
- #include <iostream>
- #include <boost/spirit.hpp>
- #include <boost/spirit/phoenix.hpp>
- using namespace std;
- using namespace boost::spirit;
- using namespace phoenix;
- struct calc_closure : boost::spirit::closure<calc_closure, double>
- {
- member1 val;
- };
- struct calculator : public grammar<calculator, calc_closure::context_t>
- {
-
- template <typename ScannerT>
- struct definition
- {
-
- typedef rule<scannert, calc_closure::context_t> rule_type;
- rule_type factor, term, exp;
-
- rule rlStart;
- const rule& start() const { return rlStart; }
-
- definition(calculator const& self)
- {
-
- factor = real_p[factor.val = arg1] |
- ('(' >> exp[factor.val = arg1] >> ')');
- term = factor[term.val = arg1] >>
- *(('*' >> factor[term.val *= arg1]) |
- ('/' >> factor[term.val /= arg1]));
- exp = term[exp.val = arg1] >>
- *(('+' >> term[exp.val += arg1]) |
- ('-' >> term[exp.val -= arg1]));
-
- rlStart = exp[self.val = arg1];
- }
- };
- };
- int main()
- {
- string strVar;
- double result;
- calculator calc;
-
- rule<phrase_scanner_t> rlEqu = (+alpha_p)[assign(strVar)] >> '=' >> calc[assign_a(result)];
- const char *szEqu = "value = 1 + (2 * (3 / (4 + 5)))";
- parse_info<> r = parse( szEqu , rlEqu, space_p);
- if(r.full)
- cout << strVar << " = " << result << endl;
- else
- cout << endl << string(r.stop - szEqu, ' ') << '^' << endl;
- return 0;
- }
若是沒拼寫出錯的話,應該會顯示出"value = 1.66667"。
例五,在四則運算表達式中使用變量
在例四中,咱們能夠解析"變量名 = 表達式"這種形式的語句。如今,咱們再進一步,容許在表達式中使用變量,如value = value * pi + 5
那麼,仍是先從規則動手。
這裏我把變量名的規則放鬆了一點,
例四裏變量名只能用字母,這裏除了第一位是字母后面容許使用數字。因而變量名規則寫成(alpha_p >> *(alnum_p))
變量表明的是一個數值,它和實數應該屬於同一級別,因此咱們把變量規則加入到factor規則裏:
- factor = real_p[factor.val = arg1] |
- (alpha_p >> *(alnum_p))[
- ('(' >> exp[factor.val = arg1] >> ')');
那麼,變量名對應的
Actor寫什麼呢?具體地說是"factor.val = 什麼"呢?
對了,咱們只要把變量名和它的數值一一對應起來,那麼這裏只要把此變量名對應的數值送給factor.val就好了,標準庫裏的
map在這裏用是再適合不過了。
爲了把變量和它的數值放到
map裏,main裏的rlEqu規則咱們也要小改改:
- rule<phrase_scanner_t> rlEqu =
- ((alpha_p >> *(alnum_p))[assign(strVar)] >>
- '=' >> calc[assign_a(result)] ) [ insert_at_a(mapVar,strVar,result) ];
後面又加了一個
Actor,把strVar及result放到
map類型的mapVar中。
回到factor規則,咱們試着把變量名規則的
Actor寫成[factor.val = getvalue(arg1, arg2)],注意全部字符串規則的
Actor都會有兩個參數,它們是兩個迭代器,分別指向起始位置和結束位置。因此這裏使用了
phoenix的arg1和arg2佔位符。
這個getvalue咱們把它寫成一個函數,它從
map中取出變量名對應的數值。
- double getvalue(const char*first, const char*last)
- {
- return mapVar[string(first,last)];
- }
編譯,出現錯誤,說不能把arg1轉換成字符串云云。看來這招不行,查
phoenix手冊,手冊說想要在
phoenix的表達式中使用函數,就得按它說的去作-_-
它的要求是這樣地:
1.先按以下形式作一個函數對象
- struct func_impl
- {
-
- template<typename Param1,typename Param2,...,typename ParamN>
- struct result{
-
- typedef returntype type;
- };
-
- template<typename Param1,typename Param2,...,typename ParamN>
- returntype operator()(...)
- {
- ...
- }
- };
2.使用phoenix::function類來包裝第一步作的函數對象,這樣才能和
phoenix配合呢
另外,也能夠直接用phoenix::bind把簡單函數包裝起來使用,不過這樣雖然簡單不少,在咱們這個例子中卻不便於封裝因而做罷(主要仍是想秀一下)。
嗯,動手作吧:
- struct getvalue_impl
- {
- template <typename ParamA,typename ParamB>
- struct result{
- typedef double type;
- };
-
- template <typename ParamA,typename ParamB>
- double operator()(ParamA const& start,ParamB const& end) const
- {
-
- return m_mapVar[string(start,end)];
- }
- getvalue_impl(map<string,double
- :m_mapVar(mapVar){;}
- private:
- map<string,double
- };
- const function<getvalue_impl> getValue = getvalue_impl();
如今終於可使用了,全部難點已經突破,能夠完工了:
- #include <iostream>
- #include <map>
- #include <boost/spirit.hpp>
- #include <boost/spirit/phoenix.hpp>
- #include <boost/spirit/actor.hpp> // insert_at_a須要
- using namespace std;
- using namespace boost::spirit;
- using namespace phoenix;
- struct calc_closure : boost::spirit::closure<calc_closure, double>
- {
- member1 val;
- };
- struct calculator : public grammar<calculator, calc_closure::context_t>
- {
- template <typename ScannerT>
- struct definition
- {
- typedef rule<scannert, calc_closure::context_t> rule_type;
- rule_type factor, term, exp;
- rule rlStart;
- const rule& start() const { return rlStart; }
- definition(calculator const& self)
- {
- factor = real_p[factor.val = arg1] |
-
- (alpha_p >> *(alnum_p))[ factor.val = self.m_getValue(arg1, arg2) ] |
- ('(' >> exp[factor.val = arg1] >> ')');
- term = factor[term.val = arg1] >>
- *(('*' >> factor[term.val *= arg1]) |
- ('/' >> factor[term.val /= arg1]));
- exp = term[exp.val = arg1] >>
- *(('+' >> term[exp.val += arg1]) |
- ('-' >> term[exp.val -= arg1]));
- rlStart = exp[self.val = arg1];
- }
- };
- calculator(map<string,double
- :m_getValue( getvalue_impl(mapVar) )
- {}
-
- struct getvalue_impl
- {
- template <typename ParamA,typename ParamB>
- struct result{
- typedef double type;
- };
-
- template <typename ParamA,typename ParamB>
- double operator()(ParamA const& start,ParamB const& end) const
- {
-
- return m_mapVar[string(start,end)];
- }
- getvalue_impl(map<string,double
- :m_mapVar(mapVar){;}
- private:
- map<string,double
- };
-
- const function<getvalue_impl> m_getValue;
- };
- void showPair(const pair<string,< span="">double> &val)
- {
- cout << val.first << " = " << val.second << endl;
- }
- int main()
- {
- string strVar;
- double result;
-
- map<string,double
-
- calculator calc(mapVar);
-
-
- rule<phrase_scanner_t> rlEqu =
- (
- (alpha_p >> *(alnum_p))[assign(strVar)] >>
- '=' >> calc[assign_a(result)] ) [ insert_at_a(mapVar,strVar,result) ];
-
- const char *szEqus[3] = {
- "PI = 3.1415926",
- "Rad = PI*2.0/3.0",
- "Deg = Rad*180/PI"};
-
- for(int i=0; i<3; i++) parse(szEqus[i], rlEqu, space_p);
-
- for_each(mapVar.begin(), mapVar.end(), showPair );
- return 0;
- }
顯示結果:
Deg = 120 PI = 2.14159 Rad = 2.0944
到如今,咱們已經能夠向別人吹噓說:腳本解析?小菜!!!哈哈...
持續改進...
例五的代碼用起來很爽吧,不算註釋的話100行不到,已經有個腳本的雛形了。只是...只是有個小問題,由於咱們設置了跳過空格,這對於語句來講是必須的,但卻帶來了一個反作用。
試試把szEqus裏的變量名中間加個空格,好比改爲"R ad = P I*2.0/3.0",這樣的語句竟然也能正確解析,這顯然不是咱們想要的(要的就是這種效果?!!偶無語...)。
那麼怎樣才能解析變量名時不準跳過空格,而解析語句的又容許跳過呢(搞雙重標準)?下面介紹的命令就能夠幫上忙了,首先趕快在沒人發現這個錯誤以前把它搞定先:
把全部的
變量名規則(factor規則定義裏有一個,rlEqu規則定義裏有一個)用
lexeme_d包裹起來:
- lexeme_d[(alpha_p >> *(alnum_p))]
再測試,嗯,如今不容許出現含有空格的變量名了。
下面介紹各類預置命令 使用形式:
命令[解析器表達式]
lexeme_d
不跳過空白字符,當工做於語法級時,解析器會忽略空白字符,lexeme_d使其臨時工做於字符級
如整數定義應該是: integer = lexeme_d[ !(ch_p('+') | '-') >> +digit ];,這樣能夠防止"1 2 345"被解析爲"12345"
as_lower_d
忽略大小寫,解析器默認是大小寫敏感的,若是要解析象
PASCAL同樣的大小寫不敏感的語法,使用r = as_lower_d["begin"];(注,裏面的參數都得小寫)
no_actions_d
中止觸發
Actor
longest_d
嘗試最長匹配
如number = integer | real;用它匹配123.456時,integer會匹配123直到遇到小數點結束,使用number=longest_d[integer | real];能夠避免這個問題。
shortest_d
與
longest_d相反
limit_d
定義範圍,用法
limit_d(min, max)[expression]
如
- uint_parser<int, 10, 2, 2> uint2_p;
- r = lexeme_d
- [
- limit_d(0u, 23u)[uint2_p] >> ':'
- >> limit_d(0u, 59u)[uint2_p] >> ':'
- >> limit_d(0u, 59u)[uint2_p]
- ];
min_limit_d/max_limit_d
定義最小/最大值,用法:
min_limit_d(min)[expression]
例七,牛叉型解析器 相對於
Spirit預置的一些
簡單解析器,它也提供了不少功能更強大的「牛叉型」解析器。現介紹以下:
f_ch_p
語法:f_ch_p(ChGenT chgen)
做用:和
ch_p相似,它解析的字符由chgen的返回值決定,chgen是一個類型爲"CharT func()"的函數(或函數對象)
例如:char X(){return 'X';}
f_ch_p(&X);
f_range_p
語法:f_range_p(ChGenAT first, ChGenBT last)
做用:和
range_p相似,它由first和last兩個函數(或函數對象)的返回值決定解析的字符範圍。
f_chseq_p
語法:f_chseq_p(IterGenAT first, IterGenBT last)
做用:和
chseq_p相似,一樣由first和last兩個函數(或函數對象)的返回值決定起始和終止迭代器。
f_str_p
語法:f_str_p(IterGenAT first, IterGenBT last)
做用:和
str_p相似,參數同
f_chseq_p
if_p
語法:if_p(condition)[then-parser].else_p[else-parser],其中.else_p能夠不要
做用:若是condition成立,就使用then-parser,不然用else-parset
例如:
if_p("0x")[hex_p]
.else_p[uint_p]
for_p
語法:for_p(init, condition, step)[body-parser]
做用:init和step是一個無參數的函數或函數對象,各參數與for的做用相似(先init,再檢查condition,有效則執行body-parser及step,再檢查condition...)
例如:
for_p(var(i)=0, var(i) < 10, ++var(i) ) [ int_p[var(sum) += arg1] ]
while_p, do_p
語法:while_p(condition)[body-parser] 及 do_p[body-parser].while_p(condition)
做用:條件循環,直接condition不成立爲止。
select_p, select_fail_p
語法:select_p(parser_a , parser_b /* ... */, parser_n);
做用:從左到右接順序測試各解析器,並獲得匹配的解析器的序號(0表示匹配parser_a,1匹配parser_b...)
例如:見
switch_p例
switch_p
語法:switch_p(value)[case_p<value_a>(parser_a),case_p<value_b>(parser_b),...,default_p(parser_def)]
做用:按value的值選擇
解析器
例如:下例中匹配的形式爲:字符a後是整數,b後是個逗號,c後跟着"bcd",d後什麼也沒有。
- int choice = -1;
- rule<> rule_select =
- select_fail_p('a', 'b', 'c', 'd')[assign_a(choice)]
- >> switch_p(var(choice))
- [
- case_p<0>(int_p),
- case_p<1>(ch_p(',')),
- case_p<2>(str_p("bcd")),
- default_p
- ];
c_escape_ch_p, lex_escape_ch_p
語法:c_escape_ch_p
做用:和
ch_p相似,其牛叉的地方在於能
解析C語言裏的轉義字符:\b, \t, , \f, , \\, \", \', \xHH, \OOO
例如:confix_p('"', *
c_escape_ch_p, '"')
repeat_p
語法、做用:
repeat_p (n) [p] 重複n次執行解析器p
repeat_p (n1, n2) [p] 重複n1到n2次解析器p
repeat_p (n, more) [p] 至少重複n次解析
例如:檢驗是不是有效的文件名
- valid_fname_chars =
- filename = repeat_p(1, 255)[valid_fname_chars];
confix_p
語法:confix_p(open,expr,close)
做用:解析獨立元素,如C語言裏的字符串,註釋等,至關於open >> (expr - close) >> close
例如:解析C註釋
confix_p("/*", *anychar_p, "*/")
comment_p,comment_nest_p
語法:comment_p(open,close),若是close不指定,默認爲回車
做用:confix_p的輔助解析器,
comment_p遇到第一個close時即返回,而
comment_nest_p要open/close對匹配才返回。
例如:
- comment_p("//") C++風格註釋
- comment_nest_p('{', '}')|comment_nest_p("(*", "*)") pascal風格註釋
list_p
語法:list_p(paser,delimiter)
做用:匹配以delimiter做爲分隔符的列表
regex_p
語法:regex_p("正則表達式")
做用:使用
正則表達式來匹配字符串(強強聯手啊~~啥也不說了)
symbols類
定義:
- template
- <
- typename T = int,
- typename CharT = char,
- typename SetT = impl::tst<t, chart>
- >
- class symbols;
初始化方式:
- symbols<> sym;
- sym = "pineapple", "orange", "banana", "apple", "mango";
- sym.add("hello", 1)("crazy", 2)("world", 3);
做用:匹配字符串(CharT*)返回對應的整數(T)
例如:
- struct Show{
- void operator()( int n ) const
- {
- cout << n;
- }
- };
- symbols<> sym;
- sym.add("零",0) ("一",1) ("二",2) ("三",3) ("四",4) ("五",5) ("六",6) ("七",7) ("八",8) ("九",9);
- parse("二零零八",*(sym[Show()]));
functor_parser
做用:能夠方便地用它來建立一個
解析器
例如:見下例
演示怎樣本身寫一個解析器,解析一個整數
- struct number_parser
- {
- typedef int result_t;
-
- template <typename ScannerT>
- std::ptrdiff_t operator()(ScannerT const& scan, result_t& result) const
- {
- if (scan.at_end())
- return -1;
- char ch = *scan;
- if (ch < '0' || ch > '9')
- return -1;
- result = 0;
- std::ptrdiff_t len = 0;
- do
- {
- result = result*10 + int(ch - '0');
- ++len;
- ++scan;
- } while (!scan.at_end() && (ch = *scan, ch >= '0' && ch <= '9'));
- return len;
- }
- };
- functor_parser<number_parser> number_parser_p;
例八,抽象語法樹(abstract syntax tree,簡稱AST)
上面的例子都是
就地解析。在比較大型的語法解析中,一種更通用的方式是先產生
抽象語法樹再遍歷它來作解析工做。好比著名的GCC,觀察它的源代碼就能夠發現,它解析源代碼時首先生成AST再開始編譯。其它編譯器由於看不到源碼很差說,想來也該是如此吧。
Spirit也支持生成
抽象語法樹的功能(不過用它來解析C++代碼可就不太合適了,Spirit針對的是輕量的小型腳本)
頭文件
#include <boost/spirit/include/classic_ast.hpp>
使用
AST和以前的解析步驟很類似,一個重要的區別是全部的子規則都應該是
字符串形式的,也就是說real_p,int_p之類的幫不上忙了,咱們得自力更生。
咱們在
例一中使用過的浮點數解析器此次能夠派上用場了。
下面的例子參考了
例四中的解析器規則:
- #include <iostream>
- #include <boost/spirit.hpp>
- #include <boost/spirit/include/classic_ast.hpp>
- using namespace std;
- using namespace boost::spirit;
- struct calculator : public grammar
- {
- template <typename ScannerT>
- struct definition
- {
- typedef rule rule_type;
- rule_type factor, term, exp, str_real_p;
- const rule_type& start() const { return exp; }
- definition(calculator const& self)
- {
- str_real_p = leaf_node_d[
- !(ch_p('+')|'-')>>+digit_p>>
- !('.'>>+digit_p)>>!((ch_p('e')|'E') >>
- !(ch_p('+')|'-')>>+digit_p)
- ];
- factor = str_real_p | inner_node_d[('(' >> exp >> ')')];
- term = factor >> *((root_node_d[ch_p('*')] >> factor)
- | (root_node_d[ch_p('/')] >> factor));
- exp = term >> *((root_node_d[ch_p('+')] >> term)
- | (root_node_d[ch_p('-')] >> term));
- }
- };
- };
- typedef tree_match<char const*>::container_t container_t;
- void showTree(const container_t& con, int Indent)
- {
- for(container_t::const_iterator itr=con.begin(); itr!=con.end(); ++itr)
- {
-
-
- cout << string(Indent*4, ' ') << "|--(" <<
- string(itr->value.begin(), itr->value.end()) << ')' << endl;
-
- showTree(itr->children, Indent+1);
- }
- }
- int main()
- {
- calculator calc;
- const char *szExq = "12 * (24 - 15) / (17 + 6)";
- tree_parse_info<> info = ast_parse(szExq, calc, space_p);
- showTree(info.trees, 0);
- return 0;
- }
這個程序能夠顯示出整個
AST的結構,好比例子中的「12 * (24 - 15) / (17 + 6)」, 解析結果(用圖片美化了一下):
這個代碼和以前的代碼主要區別是多了幾個
xxxx_node_d形式的命令,以及使用
ast_parse函數來解析。 tree_parse_info類型
ast_parse的參數與
parse相同,主要區別就在於它的返回值不是parse_info而是tree_parse_info。
tree_parse_info的成員有:
- IteratorT stop;
- bool match;
- bool full;
- std::size_t length;
- typename tree_match<IteratorT, NodeFactoryT, T>::container_t trees;
前四個和parse_info相同,多出來的trees是一個含有tree_node的容器(默認的容器是std::vector,若是預約義了BOOST_SPIRIT_USE_LIST_FOR_TREES,就會使用std::list)
tree_node有兩個重要的成員:
- children: 子節點,與tree_parse_info裏的trees類型相同:std::vector<tree_node<T>>(或std::list<...>)
- value: 數據,類型爲模板T,這個參數默認類型是node_val_data<IteratorT, ValueT>
整個
AST就是由tree_node的數據
value以及子節點
children組成的。(參考例子中showTree的代碼就能夠看出)
node_val_data<IteratorT, ValueT>的模板參數IteratorT默認是const char*, ValueT是nil_t(空數據,定義爲
struct nil_t {};)。
在這個類內部維護着一個vector(或list),它保存着解析出來的腳本字符串,好比上面例子中的"12","*","24"等。node_val_data向外提供的重要方法有:
- begin()/end(): 直接返回內部vector(或list)的begin()和end()
- is_root()/is_root(bool): 取得/設置對應節點的root狀態(由root_node_d命令設置)
- value()/value(const ValueT&)取得/設置用戶自定義數值(默認的nil_t無法帶數據,必須經過指定NodeFactoryT來改變ValueT類型,立刻會講到)
- id()/id(parser_id): 取得/設置解析此節點的解析器id(還記得rule的TagT策略嗎,下面還會講到)
它的
value()方法能夠設置和取得自定義數據,不過默認的nil_t倒是個空結構,根本不能使用。這時咱們能夠經過指定「
工廠類」來改變ValueT的類型,方法以下(假設使用
double):
- typedef node_val_data_factory<double> factory_t;
- my_grammar gram;
- my_skip_grammar skip;
- tree_parse_info<iterator_t, factory_t> i =
- ast_parse<factory_t>(first, last, gram, skip);
rule有一個
id()方法能夠返回一個parser_id類型的標記,用它能夠區分各個不一樣的
rule,它返回什麼值由TagT模板參數決定,默認的parser_address_tag返回的是
rule的內存地址。
咱們能夠用其它參數代替它以實現更適用的標記,
Spirit已準備好的TagT策略有:
parser_tag<N>,它接收一個整數,如
- rule<parser_tag > my_rule;
- assign(rule.id().to_long() == 123);
dynamic_parser_tag, 它給
rule加入了
set_id(int)的能力,如:
- rule<dynamic_parser_tag> my_dynrule;
- my_dynrule.set_id(1234);
利用這些TagT策略再和node_val_data裏的
id()相比較就能知道這個數據是由哪一個
解析器解析的。
下面介紹Spirit爲AST而引入的幾個命令:
leaf_node_d
由leaf_node_d命令包裹的規則將被視爲一個總體,它還由另外一個名字
token_node_d。
嘗試把上例中的leaf_node_d命令去掉,再看解析結果:全部的數字都被折成了一個個字節。
inner_node_d
這個命令會忽略第一個子規則和最後一個子規則,只取中間部分。
把上例中的inner_node_d去掉,那麼括號也被參與解析。
root_node_d
這個命令對於
AST相當重要,由root_node_d命令包裹的節點將成爲
同一規則中其它節點的父節點。它的工做方式以下:
假設A是前一節點 B是新產生的節點 若是B是根節點 A成爲B的第一個子節點 不然,若是A是根節點而B不是,那麼 B成爲A的最後一個子節點 其它狀況 A和B處於同一級
好比這個例子中的「12 * (24 - 15) / (17 + 6)」
對於解析器解析順序是:
exp = term term = 12{factor} *{root} (24 - 15){exp} /{root} (17 + 6){exp} ...
首先解析
12, 而後是
*, 這時發現
*是root,因而
12成爲
*的第一個子節點
接着解析
(24 - 15)這個exp,同理,
24成爲
-的第一個子節點,而後是
15,它不是root,而前一個是,因而
15成爲
-的最後一個子節點。
由於
(24 - 15)這個exp不是root,一樣成爲了
*的最後一個子節點。
再解析
/,是root, 因而把前一節點(是
*哦,由於其它的都成了
*的子節點)變成了它的首個子節點。
最後解析
(17+6)這個exp,最終成爲了
/的最後一個子節點。
no_node_d
忽略由它包裹的規則,好比例子中的:
- factor = str_real_p | inner_node_d[('(' >> exp >> ')')];
也能夠這樣表示:
- factor = str_real_p | (no_node_d[ch_p('(')] >> exp >> no_node_d[ch_p(')')]);
infix_node_d
這個命令會刪除其規則匹配出的全部節點中偶數位置上的節點,好比:
- rule_t intlist = infix_node_d[ integer >> *(',' >> integer) ];
這條規則將只產生整數數組,偶數位置上的逗號將被刪除。
discard_first_node_d/discard_last_node_d
忽略第一個/最後一個子規則(半個
inner_node_d功能) 咱們的Spirit學習先到這裏,這些只不過是Spirit裏的冰山一角,要發揮Spirit的強大威力,還得繼續前進... 在/libs/spirit/example裏有很多「很好很強大」的例子,好比:小型的C語言解釋器,XML解釋器等,你們有興趣能夠去研究研究。