Boost學習之語法解析器--Spirit

Boost.Spirit能使咱們輕鬆地編寫出一個簡單腳本的語法解析器,它巧妙利用了元編程並重載了大量的C++操做符使得咱們可以在C++裏直接使用相似EBNF的語法構造出一個完整的語法解析器(同時也把C++弄得面目全非-_-)。
關於EBNF的內容你們能夠到網上或書店裏找:css

EBNF基本形式<符號> ::= <表達式> 或 <符號> = <表達式>
表達式裏經常使用的操做符有:html

  1.     |   分隔符,表示由它分隔的某一個子表達式均可供選擇
  2.     *   重複,和正則表達式裏的*相似,表示它以前的子表達式可重複屢次
  3.     -   排除,不容許出現跟在它後面的那個子表達式
  4.     ,   串接,鏈接左右子表達式
  5.     ;   終止符,一條規則定義結束
  6.     ''  字符串
  7.     ""  字符串
  8.     (...)  分組,就是平時括號的功能啦,改變優先級用的。
  9.     (*...*) 註釋
  10.     [...]  可選,綜括號內的子表達式容許出現或不出現
  11.     {...}  重複,大括號內的子表達式能夠屢次出現
  12.     ?...?   特殊字符,由ISO定義的一些特殊字例如:

只容許賦值的簡單編程語言能夠用 EBNF 定義爲:node

  1. (* a simple program in EBNF ? Wikipedia *)
  2. program = 'PROGRAM' , white space , identifier , white space ,
  3. 'BEGIN' , white space ,
  4. { assignment , ";" , white space } ,
  5. 'END.' ;
  6. identifier = alphabetic character , [ { alphabetic character | digit } ] ;
  7. number = [ "-" ] , digit , [ { digit } ] ;
  8. string = '"' , { all characters ? '"' } , '"' ;
  9. assignment = identifier , ":=" , ( number | identifier | string ) ;
  10. alphabetic character = "A"|"B"|"C"|"D"|"E"|"F"|"G"|"H"|"I"|"J"|"K"|"L"|"M"|"N"|"O"|"P"|"Q"|"R"|"S"|"T"|"U"|"V"|"W"|"X"|"Y"|"Z" ;
  11. digit = "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9" ;
  12. white space = ? white space characters ? ;
  13. all characters = ? all visible characters ? ;

一個語法上正確的程序:ios

  1. PROGRAM DEMO1
  2. BEGIN
  3.   A0:=3;
  4.   B:=45;
  5.   H:=-100023;
  6.   C:=A;
  7.   D123:=B34A;
  8.   BABOON:=GIRAFFE;
  9.   TEXT:="Hello world!";
  10. END.

這個語言能夠輕易的擴展上控制流,算術表達式和輸入/輸出指令。就能夠開發出一個小的、可用的編程語言了。
 
因爲C++語法規則的限制,Spirit改變了EBNF中的一部分操做符的使用方式,如:git

  • 星號重複符(*)由原來的後置改成前置
  • 逗號串接符(,)由>>或&&代替
  • 中括號可選功能([表達式])改成(!表達式)
  • 大括號重複功能({表達式})由重複符(*表達式)替代
  • 取消註釋功能
  • 取消特殊字符功能
  • 同時Spirit又提供了大量的預置解析器增強了它的表達能力,所以能夠把Spirit的語法當作是一種EBNF的變種。

版本1.6.x以前的spirit能支持大部分的編譯器。在1.8.0以後,因爲spirit加入了不少C++的新特性,使兼容各類不標準的編譯器的工做變得很是鬱悶,因而Spirit再也不支持不標準的C++編譯器,這意味着VC7.1,BCB2006以及GCC3.1以前版本將再也不被支持。(注:聽說江湖上有新版Spirit的牛人修改版,能夠工做在VC6和VC7上,具體狀況不明) 正則表達式

    入門express

    頭文件:
    #include <boost/spirit.hpp>編程

    例一,解析一個浮點數    數組

    首先,要弄一個關於浮點數的EBNF規則
    假設咱們的浮點數形式是: [±]xxxx[.xxxx][Ex],其中正負號無關緊要,後面的冪無關緊要,容許不帶小數點
    則對應的EBNF規則是:
    digit = "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9";app

    real = ["+"|"-"], digit, [{digit}], [".", digit, [{digit}]], ["E"|"e", ["+"|"-"], digit, {digit}]   

    那麼對應在Spirit裏的是什麼樣的呢?

  1. !(ch_p('+')|ch_p('-'))>>+digit_p>>! (ch_p('.')>>+digit_p)>>
  2.     !((ch_p('e')|ch_p('E')) >> !(ch_p('+')|ch_p('-'))>>+digit_p)

    在Spirit中,用於匹配表達式的對象叫解析器,如這裏的ch_p, digit_p以及由它們和操做符組成的整個或部分均可以稱爲解析器

  1.     !符號表明其後的表達式是可選的,它代替了EBNF裏的中括號功能。
  2.     ch_p()是一個Spirit預置的解析器生成函數,這個解析器用於匹配單個字符
  3.     >>用於代替逗號順序鏈接後面的解析器
  4.     +符號表明1次或屢次重複
  5.     digit_p也是一個Spirit預置的解析器,它匹配數字字符

    這樣,再看上面就好理解了:可選的+-號,接着是數字,再跟着是可選的小數點和數字,最後是可選的E跟一個可接+-號的數字

    如今,把這個式子寫到代碼裏:

  1. #include <iostream> 
  2. #include <boost/spirit.hpp>
  3. using namespace std;
  4. using namespace boost::spirit;
  5. int main()
  6. {
  7.     parse_info<> r = parse("-12.33E-10",
  8.         !(ch_p('+')|ch_p('-'))>>+digit_p>>
  9.         !(ch_p('.')>>+digit_p)>>
  10.         !((ch_p('e')|ch_p('E')) >>
  11.         !(ch_p('+')|ch_p('-'))>>+digit_p)
  12.         );
  13.     cout << "parsed " << (r.full?"successful":"failed") << endl;
  14.     return 0;
  15. }

    這就是Spirit,這個變種的EBNF語法直接就寫在C++代碼裏就能夠了,實際上它們是由一系列的簡單解析器對象經過重載操做符後組合而成的複雜解析器
    解析器重載的操做符也能夠幫咱們自動做一些轉換工做,如上面的式子中ch_p('+')|ch_p('-')就能夠改爲ch_p('+')|'-',只要左邊或右邊的數值其中之一是解析器,它就能自動和另外一邊的數值組合。
    簡化後以下:

  1. !(ch_p('+')|'-')>>+digit_p>>!('.'>>+digit_p)>>!((ch_p('e')|'E') >> !(ch_p('+')|'-')>>+digit_p)

    parse函數調用解析器來解析指定的字符串,它的原型是:

  1. parse_info<charT const*> parse(字符串, 解析器); 
  2. parse_info<charT const*> parse(字符串, 解析器1, 解析器2); 

    第二個版本中的解析器2指出解析時能夠忽略的一些字符,好比語句中的空格之類的。
    另外,parse還有迭代器的版本

  1. parse_info parse(IteratorT first, IteratorT last, 解析器);
  2. parse_info parse(IteratorT first, IteratorT last, 解析器1, 解析器2);   

    IteratorT能夠是任何迭代器類包括字符串指針,前面的這個兩個版本其實只是簡單地包裝了一下這兩個函數。
    返回的parse_info類(其中的IteratorT模板默認爲char const*)包含了解析結果信息,裏面的成員有:

  1. IteratorT   stop;   //最後解析的位置
  2. bool        hit;    //是否與整個解析器匹配
  3. bool        full;   //是否與整個字符串匹配
  4. std::size_t length; //解析器解析了多少個字符,注意,first+length不必定與stop相同

    其實,Spirit已經幫咱們準備好了不少解析器,好比上面咱們寫得要死的浮點數匹配,只要一個real_p就好了(冷靜,冷靜,上面的一長串到後面仍是會用到的)

  1. parse_info<> r = parse("-12.33E-10",real_p);

Spirit預置的一些原始解析器,它們的名字都是以"xxxx_p"的形式出現。
字符解析器

  • ch_p('X') 返回單字符解析器
  • range_p('a','z')    返回一個字符範圍解析器,本例中匹配'a'..'z'
  • str_p("Hello World")    返回一個字符串解析器
  • chseq_p("ABCDEFG")  返回一個字符序列解析器,它能夠匹配"ABCDEFG","A B C D E F G","AB CD EFG"等
  • anychar_p 匹配任何字符(包括'\0')
  • alnum_p 匹配A-Z,a-z,0-9
  • alpha_p 匹配字母
  • blank_p 匹配空格和TAB
  • cntrl_p 匹配控制字符
  • digit_p 匹配數字字符
  • graph_p 匹配可顯示字符(除空格,回車,TAB等)
  • lower_p 匹配小寫字符
  • print_p 匹配可打印字符
  • punct_p 匹配標點符號
  • space_p 匹配空格,回車,換行,TAB
  • upper_p 匹配大寫字符
  • xdigit_p 匹配十六進制數字符串
  • eol_p   匹配行尾
  • nothing_p 不匹配任何字符,老是返回Fail(不匹配)
  • end_p   匹配結尾

字符解析器支持的操做符

  • ~a      排除操做,如~ch_p('x')表示排除'x'字符
  • a|b     二選一操做,或稱爲聯合,匹配a or b
  • a&b    交集,同時匹配a和b
  • a-b     差,匹配a但不匹配b
  • a^b    異或,匹配a 或 匹配b,但不能二者同時匹配
  • a>>b  序列鏈接,按順序先匹配a,接下來的字符再匹配b
  • a&&b  同上(象C語言同樣,有短路效果,若a不匹配,則b不會被執行)
  • a||b    連續或,按順序先匹配a,接下來的字符匹配b(象C語言同樣,有短路效果,若a已匹配,則b不會被執行)
  • *a      匹配0次或屢次
  • +a      匹配1次或屢次
  • !a       可選,匹配0或1次
  • a%b   列表,匹配a b a b a b a...,效果與 a >> *(b >> a)相同

整數解析器    Spirit給咱們準備了兩個整數解析器類,對應於有符號數和無符號數int_parser和uint_parser
    它們都是模板類,定義以下:

  1. template <
  2.         typename T = int,
  3.         int Radix = 10,
  4.         unsigned MinDigits = 1,
  5.         int MaxDigits = -1>
  6.     struct int_parser;
  7. template <
  8.         typename T = unsigned,
  9.         int Radix = 10,
  10.         unsigned MinDigits = 1,
  11.         int MaxDigits = -1>
  12.     struct uint_parser;

模板參數用法:

  • T爲數字類型
  • Radix爲進制形式
  • MinDigits爲最小長度
  • MaxDigits爲最大長度,若是是-1表示不限制

好比下面這個例子能夠匹配象 1,234,567,890 這種形式的數字

  1. uint_parser<unsigned, 10, 1, 3> uint3_p;        //  1..3 digits
  2. uint_parser<unsigned, 10, 3, 3> uint3_3_p;      //  exactly 3 digits
  3. ts_num_p = (uint3_p >> *(',' >> uint3_3_p));    //  our thousand separated number parser

Spirit已預置的幾個int_parser/uint_parser的特化版本:

  1. int_p int_parser<int, 10, 1, -1> const
  2. bin_p uint_parser<unsigned, 2, 1, -1> const
  3. oct_p uint_parser<unsigned, 8, 1, -1> const
  4. uint_p uint_parser<unsigned, 10, 1, -1> const
  5. hex_p uint_parser<unsigned, 16, 1, -1> const

實數解析器Spirit固然也會給咱們準備實數解析器,定義以下:

  1. template<
  2.     typename T = double,
  3.     typename RealPoliciesT = ureal_parser_policies >
  4. struct real_parser;

模板參數用法:

  • T表示實數類型
  • RealRoliciesT是一個策略類,目前不用深究,只要知道它決定了實數解析器的行爲就好了。

已預置的實數解析器的特化版本:

  1. ureal_p real_parser<double, ureal_parser_policies<double=""> > const
  2. real_p real_parser<double, real_parser_policies<double=""> > const
  3. strict_ureal_p real_parser<double, strict_ureal_parser_policies<double=""> > const
  4. strict_real_p real_parser<double, strict_real_parser_policies<double=""> > const

    real_p前面實例裏已經見過,ureal_p是它的unsigned版本。strict_*則更嚴格地匹配實數(它不匹配整數)

例二,解析實數序列  
    有了上面的知識,咱們能夠試試解析以逗號分隔的實數序列
    字符串形式爲"real,real,real,...real"
    參考上面的一堆預置解析器,咱們能夠這樣組合:
  1. real_p >> *(',' >> real_p);
    更簡單點,咱們可使用%操做符
  1. real_p%','
    因而很簡單地寫下這樣的代碼:
  1. {
  2.     //用於解析的字符串
  3.     const char *szNumberList = "12.4,1000,-1928,33,30";
  4.     parse_info<> r = parse( szNumberList, real_p % ',' );
  5.     cout << "parsed " << (r.full?"successful":"failed") << endl;
  6.     cout << szNumberList << endl;
  7.     //使用parse_info::stop肯定最後解析的位置便於查錯
  8.     cout << string(r.stop - szNumberList, ' ') << '^' << endl; 
  9. }
    解析成功!接下來咱們就把裏面的數字取出來,解析器重載了[]操做符,在這裏能夠放入 函數或函數對象,放在這裏面的函數或函數對象在Spirit裏稱之爲 Actor
    對於real_p,它要求形式爲:void func(double v)的 函數或函數對象,下面咱們就來取出這些數字:
  1. #include <iostream>
  2. #include <boost/spirit.hpp>
  3. using namespace std;
  4. using namespace boost::spirit;
  5. //定義函數做爲解析器的Actor
  6. void showreal(double v)
  7. {
  8.     cout << v << endl;
  9. }
  10. int main()
  11. {
  12.     //用於解析的字符串
  13.     const char *szNumberList = "12.4,1000,-1928,33,30"; 
  14.     //加入函數
  15.     parse_info<> r = parse( szNumberList, real_p[&showreal] % ',' );
  16.     cout << "parsed " << (r.full?"successful":"failed") << endl;
  17.     cout << szNumberList << endl;
  18.     //使用parse_info::stop肯定最後解析的位置便於查錯
  19.     cout << string(r.stop - szNumberList, ' ') << '^' << endl;
  20.     return 0;
  21. }
    再次運行,顯示了一列數字了吧:)
    再寫一個函數對象版本的,此次把這列數字寫到 vector
  1. #include <iostream>
  2. #include <vector>
  3. #include <boost/spirit.hpp>
  4. using namespace std;
  5. using namespace boost::spirit;
  6. int main()
  7. {
  8.     // pushreal函數對象,把數字放入vector中
  9.     struct pushreal
  10.     {
  11.         void operator()(double v) const
  12.         {
  13.             m_vec.push_back(v);
  14.         }
  15.         pushreal(vector<double> &vec)
  16.             :m_vec(vec){;}
  17.         private:
  18.         vector<double> &m_vec;
  19.     };
  20.     vector<double> reallist;
  21.     //用於解析的字符串
  22.     const char *szNumberList = "12.4,1000,-1928,33,30";
  23.     //此次用pushreal對象做爲Actor
  24.     parse_info<> r = parse( szNumberList, real_p[pushreal(reallist)] % ',' );
  25.     cout << "parsed " << (r.full?"successful":"failed") << endl;
  26.     cout << szNumberList << endl;
  27.     //使用parse_info::stop肯定最後解析的位置便於查錯
  28.     cout << string(r.stop - szNumberList, ' ') << '^' << endl;
  29.     //顯示結果
  30.     copy(reallist.begin(),reallist.end(),ostream_iterator<double>(cout," "));
  31.     return 0;
  32. }
    我不得不告訴你, Spirit也提供了比偶的這個pushreal強得多的函數對象 push_back_a(須要冷靜哈)
  1. #include <iostream>
  2. #include <vector>
  3. #include <boost/spirit.hpp>
  4. using namespace std;
  5. using namespace boost::spirit;
  6. int main()
  7. {
  8.     vector<double> reallist;
  9.     //用於解析的字符串
  10.     const char *szNumberList = "12.4,1000,-1928,33,30"; 
  11.     //使用自帶的push_back_a
  12.     parse_info<> r = parse( szNumberList, real_p[push_back_a(reallist)] % ',' );
  13.     cout << "parsed " << (r.full?"successful":"failed") << endl;
  14.     cout << szNumberList << endl;
  15.     //使用parse_info::stop肯定最後解析的位置便於查錯
  16.     cout << string(r.stop - szNumberList, ' ') << '^' << endl;
  17.     //顯示結果
  18.     copy(reallist.begin(),reallist.end(),ostream_iterator<double>(cout," "));
  19.     return 0;
  20. }
    在上面的實數序列中,若是中間含有空格或TAB,這個解析就不能成功,這時可使用 parse函數的另外一個重載版本:
  1. parse_info<charT const*> parse(字符串, 解析器1, 解析器2);
  2. //或
  3. parse_info parse(IteratorT first, IteratorT last, 解析器1, 解析器2);  
    其中的解析器2用於跳過其匹配的字符,咱們要跳過空格,因此解析器2可使用 space_p
  1. parse_info<> r = parse( szNumberList,
  2.     real_p[push_back_a(reallist)] % ',',
  3.     space_p);
    若是更進一步,咱們甚至能夠連逗號也跳過,直接取得一列數字:
  1. parse_info<> r = parse( szNumberList,
  2.     *real_p[push_back_a(reallist)],
  3.     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解析器組合:

  1. rule<phrase_scanner_t> factor, term, exp;
  2. factor = real_p | ('(' >> exp >> ')');
  3. term   = factor >> *(('*' >> factor) | ('/' >> factor));
  4. exp    = term >> *(('+' >> term) | ('-' >> term));

    這裏的rule是一個規則類,它能夠做爲全部解析器的佔位符,定義以下:

  1. template<
  2. typename ScannerT = scanner<>,
  3. typename ContextT = parser_context<>,
  4. typename TagT = parser_address_tag>
  5. class rule;

    其中的模板參數做用是:
ScannerT    掃描器策略類
        它有兩類工做模式,一種是字符模式,一種是語法模式,默認的scanner<>是工做於字符模式的。
ContextT    內容策略類
        它決定了rule裏的成員變量以及Actor的類型,稍後會有利用這個模板參數來加入自定義的成員變量的例子
TagT          標識策略類
        每一個rule都有一個id()方法,用於識別不一樣的rule,TagT就用於決定id()返回的數據(後面會講到)。
    這三個策略類能夠不按順序地輸入,如

  1. rule<parser_address_tag,parser_context<>,scanner<> >;
  2. rule<parser_context<> >;
  3. rule<scanner<>,parser_address_tag >;

    是同一個類。
    值得注意的是ScannerT,咱們上面沒有使用默認的scanner<>,而是使用了phrase_scanner_t,由於工做於字符模式的掃描器沒法與parse的解析器2參數(跳過匹配字符,見上)一同工做,這樣就沒法解析含有空格的表達式,這可不完美,因此咱們使用的工做於語法模式phrase_scanner_t

  1. #include <iostream>
  2. #include <vector>
  3. #include <boost/spirit.hpp>
  4. using namespace std;
  5. using namespace boost::spirit;
  6. int main()
  7. {
  8.     rule<phrase_scanner_t> factor, term, exp;
  9.     factor = real_p | ('(' >> exp >> ')');
  10.     term   = factor >> *(('*' >> factor) | ('/' >> factor));
  11.     exp    = term >> *(('+' >> term) | ('-' >> term));
  12.     const char *szExp = "1 + (2 * (3 / (4 + 5)))";
  13.     parse_info<> r = parse( szExp , exp, space_p);
  14.     cout << "parsed " << (r.full?"successful":"failed") << endl;
  15.     return 0;
  16. }

    接下來,要獲得這個四則表達式的計算結果,這纔是咱們要的,因而Spirit自帶的lambda支持:phoenix登場!

    頭文件:
    #include <boost/spirit/phoenix.hpp>     

    phoenix提供和與Boost.Lambda相似的功能,它能夠直接就地生成匿名函數對象phoenix使用arg1,arg2,arg3...做爲佔位符,Boost.Lambda則使用_1,_2,_3...,使用舉例:

  1. #include <iostream>
  2. #include <vector>
  3. #include <boost/spirit.hpp>
  4. #include <boost/spirit/phoenix.hpp>
  5. using namespace std;
  6. using namespace boost::spirit;
  7. using namespace phoenix;
  8. int main()
  9. {
  10.     vector<int> vec(10);
  11.     int i=0;
  12.     //arg1 = var(i)++ 把i++賦值給vec裏各單元
  13.     for_each(vec.begin(),vec.end(),arg1 = var(i)++);
  14.     //cout<<arg1<<endl 把vec各單元輸出至cout
  15.     for_each(vec.begin(),vec.end(),cout << arg1 << endl);
  16.     return 0;
  17. }

    這樣咱們就能夠利用phoenix提供的匿名函數對象做爲Actor, 同時利用Spirit提供的closure類爲rule添加一個val成員變量存儲計算結果(還記得rule的ContextT策略嗎?)

  1. #include <iostream>
  2. #include <vector>
  3. #include <boost/spirit.hpp>
  4. #include <boost/spirit/phoenix.hpp>
  5. using namespace std;
  6. using namespace boost::spirit;
  7. using namespace phoenix;
  8. int main()
  9. {
  10.     //爲rule準備一個val變量,類型爲double
  11.     //準確地說:是一個phoenix類(這裏的member1),它和其它phoenix類組成lambda表達式,在lambda中能夠把它當作是一個double。      
  12.     struct calc_closure : boost::spirit::closure<calc_closure, double>
  13.     {
  14.         member1 val;
  15.     };
  16.     //定義ContextT策略爲calc_closure::context_t
  17.     rule<phrase_scanner_t, calc_closure::context_t> factor, term, exp;
  18.     //直接使用phoenix的lambda表達式做爲Actor
  19.     factor = real_p[factor.val = arg1] | ('(' >> exp[factor.val = arg1] >> ')');
  20.     term   = factor[term.val = arg1] >> *(('*' >> factor[term.val *= arg1]) | ('/' >> factor[term.val /= arg1]));
  21.     exp    = term[exp.val = arg1] >> *(('+' >> term[exp.val += arg1]) | ('-' >> term[exp.val -= arg1]));
  22.     const char *szExp = "1 + (2 * (3 / (4 + 5)))";
  23.     double result;
  24.     parse_info<> r = parse( szExp , exp[assign_a(result)], space_p);
  25.     cout << szExp;
  26.     if(r.full)
  27.     {
  28.         //成功,獲得結果
  29.         cout << " = " << result << endl;
  30.     }
  31.     else
  32.     {
  33.         //失敗,顯示錯誤位置
  34.         cout << endl << string(r.stop - szExp, ' ') << '^' << endl;
  35.     }
  36.     return 0;
  37. }

    感到很神奇?這裏有必要多說一下boost::spirit::closure的做用,它的使用方法是:

  1. struct name : spirit::closure<name, type1, type2, type3,... typen>
  2. {
  3.     member1 m_name1;
  4.     member2 m_name2;
  5.     member3 m_name3;
  6.     ...
  7.     memberN m_nameN;
  8. };

    一種類型對應一個member,使用name::context_t做爲ContextT策略的rule就會含有N個相應的變量,並且這個rule的Actor將會接收到member1對應的數據。
    也能夠用於語法類,如grammar<t, name::context_t="">,關於語法類,後面章節將會提到。
    注:默認最多到member3,要想使用更多數據,在包含Spirit頭文件前預約義PHOENIX_LIMIT和BOOST_SPIRIT_CLOSURE_LIMIT,如

  1. #define PHOENIX_LIMIT 10
  2. #define BOOST_SPIRIT_CLOSURE_LIMIT 10
提升 例四,使用自定義語法類框架
    在 例三中,咱們用了不足50行的代碼搞定了一個四則運算字符串的解析,可見Spirit的威力巨大。不過僅僅這樣還不夠,Spirit但是號稱輕量語法解析庫,一個基本可用的腳本可不止四則運算那麼點東西,起碼要有賦值、條件、循環、輸入輸出吧?
    有了上面的知識,再加上一些編程經驗,一個個搞定它們應該不是太難的事,但把全部的規則堆在一塊兒不只噁心,並且難以維護,因而Spirit提供了語法類 grammar來集中管理。
     grammar的定義以下:
  1. template<
  2.     typename DerivedT,
  3.     typename ContextT = parser_context<> >
  4. struct grammar;
    DerivedT參數是反引用自身類型,若是用過 WTL庫的可能對這個比較熟悉,使用這種技術能夠保持多態性的同時消除虛函數帶來的性能開稍。
    ContextT參數就是 內容策略類,在 例三中提到過。
    編寫一個 語法類框架的基本形式以下:
  1. struct my_grammar : public grammar<my_grammar>
  2. {
  3.     template <typename ScannerT>
  4.     struct definition
  5.     {
  6.         rule  r;
  7.         definition(my_grammar const& self)  { r = /*..define here..*/; }
  8.         rule const& start() const { return r; }
  9.     };
  10. };
    它繼承自 grammar,模板參數DerivedT就是自身類型,ContextT可使用默認的parser_context<>或者本身定義一個(好比 例三中的 closure)。
    這個類內部必需要有一個 definition類的定義,這個 definition類的模板參數ScannerT由框架使用環境決定。它由兩個重要方法:
  • start() const函數:它返回一個rule。使用my_grammar解析時,就從這個rule開始。
  • definition構造函數:這裏是初始化rule的最好場所。它的self參數是整個my_grammar的實例引用,接下去你會發現這但是個頗有用的東西。
    同時,很重要的一點: 語法類自己也是一個 解析器,它也能與其它 解析器組合。

    下面,咱們把例三中的四則運算解析功能放到一個語法類中,而後再用這個語法類與其它解析器合做弄一個簡單的賦值操做出來:
  1. #include <iostream>
  2. #include <boost/spirit.hpp>
  3. #include <boost/spirit/phoenix.hpp>
  4. using namespace std;
  5. using namespace boost::spirit;
  6. using namespace phoenix;
  7. //closure,爲解析器提供存儲策略,見例三
  8. struct calc_closure : boost::spirit::closure<calc_closure, double>
  9. {
  10.     member1 val;
  11. };
  12. //四則運算語法類,它也使用了closure的內容策略
  13. struct calculator : public grammar<calculator, calc_closure::context_t>
  14. {
  15.     //語法類重要成員:struct definition
  16.     template <typename ScannerT>
  17.     struct definition
  18.     {
  19.         // factor, term, exp的rule類型,同例三(ScannerT模板在使用時決定)
  20.         typedef rule<scannert, calc_closure::context_t> rule_type;
  21.         rule_type factor, term, exp;
  22.         // 啓動rule,在這個例子中,它也是遞歸的最頂層,負責把exp的最終結果賦值給框架自己。
  23.         rule rlStart;
  24.         const rule& start() const { return rlStart; }
  25.         //definition的構造函數,self參數引用的是calculator類的實例
  26.         definition(calculator const& self)
  27.         {
  28.             // 四則運算規則定義與例三相同
  29.             factor = real_p[factor.val = arg1] |
  30.                 ('(' >> exp[factor.val = arg1] >> ')');
  31.             term   = factor[term.val = arg1] >>
  32.                 *(('*' >> factor[term.val *= arg1]) |
  33.                         ('/' >> factor[term.val /= arg1]));
  34.             exp    = term[exp.val = arg1] >>
  35.                 *(('+' >> term[exp.val += arg1]) |
  36.                         ('-' >> term[exp.val -= arg1]));
  37.             //self.val=arg1也是phoenix的匿名函數:把exp的結果賦值給框架自己(self的做用)
  38.             rlStart  = exp[self.val = arg1];
  39.         }
  40.     };
  41. };
  42. int main()
  43. {
  44.     string strVar; //變量名
  45.     double result; //結果
  46.     calculator calc;
  47.     // 賦值語法:變量名 = 表達式
  48.     rule<phrase_scanner_t> rlEqu =  (+alpha_p)[assign(strVar)] >> '=' >> calc[assign_a(result)];
  49.     const char *szEqu = "value = 1 + (2 * (3 / (4 + 5)))";
  50.     parse_info<> r = parse( szEqu , rlEqu, space_p);
  51.     if(r.full) //成功,獲得結果
  52.         cout << strVar << " = " << result << endl;
  53.     else  //失敗,顯示錯誤位置
  54.         cout << endl << string(r.stop - szEqu, ' ') << '^' << endl;
  55.     return 0;
  56. }
    若是沒拼寫出錯的話,應該會顯示出"value = 1.66667"。
 
    例五,在四則運算表達式中使用變量
    在例四中,咱們能夠解析"變量名 = 表達式"這種形式的語句。如今,咱們再進一步,容許在表達式中使用變量,如value = value * pi + 5
    那麼,仍是先從規則動手。
    這裏我把變量名的規則放鬆了一點, 例四裏變量名只能用字母,這裏除了第一位是字母后面容許使用數字。因而變量名規則寫成(alpha_p >> *(alnum_p))
    變量表明的是一個數值,它和實數應該屬於同一級別,因此咱們把變量規則加入到factor規則裏:
  1. factor = real_p[factor.val = arg1] |
  2. // 在表達式中使用變量
  3. (alpha_p >> *(alnum_p))[/*這裏寫什麼呢*/]|
  4. ('(' >> exp[factor.val = arg1] >> ')');
    那麼,變量名對應的 Actor寫什麼呢?具體地說是"factor.val = 什麼"呢?
    對了,咱們只要把變量名和它的數值一一對應起來,那麼這裏只要把此變量名對應的數值送給factor.val就好了,標準庫裏的 map在這裏用是再適合不過了。
    爲了把變量和它的數值放到 map裏,main裏的rlEqu規則咱們也要小改改:
  1. rule<phrase_scanner_t> rlEqu =
  2.     ((alpha_p >> *(alnum_p))[assign(strVar)] >>
  3.     '=' >> 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中取出變量名對應的數值。
  1. double getvalue(const char*first, const char*last)
  2. {
  3.     return mapVar[string(first,last)];
  4. }
    編譯,出現錯誤,說不能把arg1轉換成字符串云云。看來這招不行,查 phoenix手冊,手冊說想要在 phoenix的表達式中使用函數,就得按它說的去作-_-
    它的要求是這樣地:
    1.先按以下形式作一個函數對象
  1. struct func_impl
  2. {
  3.     //Param1等對就的是各個輸入參數的類型
  4.     template<typename Param1,typename Param2,...,typename ParamN>
  5.     struct result{
  6.          //定義輸出參數的類型
  7.          typedef returntype type;
  8.     };
  9.     //在這裏該幹啥幹啥
  10.     template<typename Param1,typename Param2,...,typename ParamN>
  11.     returntype operator()(...)
  12.     {
  13.         ...
  14.     }
  15. };
 
    2.使用phoenix::function類來包裝第一步作的函數對象,這樣才能和 phoenix配合呢

    另外,也能夠直接用phoenix::bind把簡單函數包裝起來使用,不過這樣雖然簡單不少,在咱們這個例子中卻不便於封裝因而做罷(主要仍是想秀一下)。
    嗯,動手作吧:
  1. //適配phoenix的函數對象
  2. struct getvalue_impl
  3. {
  4.     template <typename ParamA,typename ParamB>    //輸入參數類型
  5.         struct result{
  6.             typedef double type;    //返回類型
  7.         };
  8.     //函數主體,其實這裏的ParamA和ParamB都是char*
  9.     template <typename ParamA,typename ParamB>
  10.         double operator()(ParamA const& start,ParamB const& end) const
  11.         {
  12.             //返回變量名對應的數值
  13.             return m_mapVar[string(start,end)];
  14.         }
  15.     getvalue_impl(map<string,double
  16.         :m_mapVar(mapVar){;}
  17.     private:
  18.     map<string,double
  19. };
  20. // phoenix表達式中能接受的仿函數類型
  21. const function<getvalue_impl> getValue = getvalue_impl();
    如今終於可使用了,全部難點已經突破,能夠完工了:
  1. #include <iostream>
  2. #include <map>
  3. #include <boost/spirit.hpp>
  4. #include <boost/spirit/phoenix.hpp>
  5. #include <boost/spirit/actor.hpp>    // insert_at_a須要
  6. using namespace std;
  7. using namespace boost::spirit;
  8. using namespace phoenix;
  9. struct calc_closure : boost::spirit::closure<calc_closure, double>
  10. {
  11.     member1 val;
  12. };
  13. struct calculator : public grammar<calculator, calc_closure::context_t>
  14. {
  15.     template <typename ScannerT>
  16.     struct definition
  17.     {
  18.         typedef rule<scannert, calc_closure::context_t> rule_type;
  19.         rule_type factor, term, exp;
  20.         rule rlStart;
  21.         const rule& start() const { return rlStart; }
  22.         definition(calculator const& self)
  23.         {
  24.             factor = real_p[factor.val = arg1] |
  25.                 // 容許在表達式中使用變量,結果用calculator::m_getValue從map中取
  26.                 (alpha_p >> *(alnum_p))[ factor.val = self.m_getValue(arg1, arg2) ] |
  27.                 ('(' >> exp[factor.val = arg1] >> ')');
  28.             term   = factor[term.val = arg1] >>
  29.                 *(('*' >> factor[term.val *= arg1]) |
  30.                 ('/' >> factor[term.val /= arg1]));
  31.             exp    = term[exp.val = arg1] >>
  32.                 *(('+' >> term[exp.val += arg1]) |
  33.                 ('-' >> term[exp.val -= arg1]));
  34.             rlStart  = exp[self.val = arg1];           
  35.         }
  36.     };
  37.     calculator(map<string,double
  38.         :m_getValue( getvalue_impl(mapVar) )    //初始化,把map傳給m_getValue
  39.     {}
  40.     //適配phoenix的函數對象
  41.     struct getvalue_impl
  42.     {
  43.         template <typename ParamA,typename ParamB>    //輸入參數類型
  44.         struct result{
  45.             typedef double type;    //返回類型
  46.         };
  47.         //函數主體,其實這裏的ParamA和ParamB都是char*
  48.         template <typename ParamA,typename ParamB>
  49.         double operator()(ParamA const& start,ParamB const& end) const
  50.         {
  51.             //返回變量名對應的數值
  52.             return m_mapVar[string(start,end)];
  53.         }
  54.         getvalue_impl(map<string,double
  55.             :m_mapVar(mapVar){;}
  56.         private:
  57.         map<string,double
  58.     };
  59.     // phoenix表達式中能接受的仿函數類型
  60.     const function<getvalue_impl> m_getValue;
  61. };
  62. //用來顯示map中變量的值
  63. void showPair(const pair<string,< span="">double> &val)
  64. {
  65.     cout << val.first << " = " << val.second << endl;
  66. }
  67. int main()
  68. {
  69.     string strVar;
  70.     double result;
  71.     //用來保存變量和對應的數值
  72.     map<string,double
  73.     //把map傳給語法類,讓解析器知道變量的值
  74.     calculator calc(mapVar);
  75.     // 變量名規則(alpha_p >> +(alnum_p)),除第一位外後面能夠跟數字。
  76.     // 整個等式末尾加入insert_at_a的actor,匹配成功後把變量和數值存到map中。
  77.     rule<phrase_scanner_t> rlEqu =
  78.         (
  79.          (alpha_p >> *(alnum_p))[assign(strVar)] >>
  80.          '=' >> calc[assign_a(result)] ) [ insert_at_a(mapVar,strVar,result) ];
  81.     // 多行賦值語句,表達式用使用變量
  82.     const char *szEqus[3] = {
  83.         "PI = 3.1415926",
  84.         "Rad = PI*2.0/3.0",
  85.         "Deg = Rad*180/PI"};
  86.     // 逐句解析
  87.     for(int i=0; i<3; i++)    parse(szEqus[i], rlEqu, space_p);
  88.     // 顯示每一個變量的數值
  89.     for_each(mapVar.begin(), mapVar.end(), showPair );
  90.     return 0;
  91. }
顯示結果:
    Deg = 120     PI = 2.14159     Rad = 2.0944
到如今,咱們已經能夠向別人吹噓說:腳本解析?小菜!!!哈哈...
 
持續改進...     例五的代碼用起來很爽吧,不算註釋的話100行不到,已經有個腳本的雛形了。只是...只是有個小問題,由於咱們設置了跳過空格,這對於語句來講是必須的,但卻帶來了一個反作用。
    試試把szEqus裏的變量名中間加個空格,好比改爲"R ad = P   I*2.0/3.0",這樣的語句竟然也能正確解析,這顯然不是咱們想要的(要的就是這種效果?!!偶無語...)。
    那麼怎樣才能解析變量名時不準跳過空格,而解析語句的又容許跳過呢(搞雙重標準)?下面介紹的命令就能夠幫上忙了,首先趕快在沒人發現這個錯誤以前把它搞定先:
    把全部的 變量名規則(factor規則定義裏有一個,rlEqu規則定義裏有一個)用 lexeme_d包裹起來:
  1. 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]
    如
  1. uint_parser<int, 10, 2, 2> uint2_p;
  2. r = lexeme_d
  3. [
  4.     limit_d(0u, 23u)[uint2_p] >> ':'    //  Hours 00..23
  5.         >>  limit_d(0u, 59u)[uint2_p] >> ':'    //  Minutes 00..59
  6.         >>  limit_d(0u, 59u)[uint2_p]           //  Seconds 00..59
  7. ];
     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後什麼也沒有。
  1. int choice = -1;
  2. rule<> rule_select =
  3.     select_fail_p('a', 'b', 'c', 'd')[assign_a(choice)]
  4.         >> switch_p(var(choice))
  5.             [
  6.                 case_p<0>(int_p),
  7.                 case_p<1>(ch_p(',')),
  8.                 case_p<2>(str_p("bcd")),
  9.                 default_p
  10.             ];
     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次解析 
    
        例如:檢驗是不是有效的文件名
  1. valid_fname_chars = /*..*/;
  2. 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對匹配才返回。
        例如:
  1. comment_p("//") C++風格註釋
  2. comment_nest_p('{', '}')|comment_nest_p("(*", "*)") pascal風格註釋
     list_p
        語法:list_p(paser,delimiter)
        做用:匹配以delimiter做爲分隔符的列表
        
     regex_p
        語法:regex_p("正則表達式")
        做用:使用 正則表達式來匹配字符串(強強聯手啊~~啥也不說了)
        
     symbols類
        定義:
  1. template
  2. <
  3.     typename T = int,
  4.     typename CharT = char,
  5.     typename SetT = impl::tst<t, chart>
  6. >
  7. class symbols;
        初始化方式:
  1. symbols<> sym;
  2. sym = "pineapple", "orange", "banana", "apple", "mango";
  3. sym.add("hello", 1)("crazy", 2)("world", 3);
        做用:匹配字符串(CharT*)返回對應的整數(T)
        例如:
  1. struct Show{
  2.     void operator()( int n ) const
  3.     {
  4.         cout << n;
  5.     }
  6. };
  7. symbols<> sym;
  8. sym.add("零",0) ("一",1) ("二",2) ("三",3) ("四",4) ("五",5) ("六",6) ("七",7) ("八",8) ("九",9);
  9. parse("二零零八",*(sym[Show()])); 
     functor_parser
        做用:能夠方便地用它來建立一個 解析器
        例如:見下例
演示怎樣本身寫一個解析器,解析一個整數
  1. struct number_parser
  2. {
  3.     typedef int result_t;   //定義解析器結果類型
  4.     //參數是:掃描器,結果
  5.     template <typename ScannerT>
  6.     std::ptrdiff_t operator()(ScannerT const& scan, result_t& result) const
  7.     {
  8.         if (scan.at_end())  //若是結果或出錯,返回-1
  9.             return -1;
  10.         char ch = *scan;
  11.         if (ch < '0' || ch > '9')
  12.             return -1;
  13.         result = 0;
  14.         std::ptrdiff_t len = 0;
  15.         do              //解析字符串,獲得結果
  16.         {
  17.             result = result*10 + int(ch - '0');    
  18.             ++len;
  19.             ++scan;
  20.         } while (!scan.at_end() && (ch = *scan, ch >= '0' && ch <= '9'));
  21.         return len; //返回解析的字符串長度
  22.     }
  23. };
  24. //用functor_parser包裝成解析器
  25. 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之類的幫不上忙了,咱們得自力更生。
    咱們在 例一中使用過的浮點數解析器此次能夠派上用場了。    
    下面的例子參考了 例四中的解析器規則:
  1. #include <iostream>
  2. #include <boost/spirit.hpp>
  3. #include <boost/spirit/include/classic_ast.hpp>
  4. using namespace std;
  5. using namespace boost::spirit;
  6. struct calculator : public grammar
  7. {
  8.     template <typename ScannerT>
  9.     struct definition
  10.     {
  11.         typedef rule rule_type;
  12.         rule_type factor, term, exp, str_real_p;
  13.         const rule_type& start() const { return exp; }
  14.         definition(calculator const& self)
  15.         {
  16.             str_real_p = leaf_node_d[
  17.                 !(ch_p('+')|'-')>>+digit_p>>
  18.                 !('.'>>+digit_p)>>!((ch_p('e')|'E') >>
  19.                         !(ch_p('+')|'-')>>+digit_p)
  20.             ];
  21.             factor = str_real_p | inner_node_d[('(' >> exp >> ')')];
  22.             term   = factor >> *((root_node_d[ch_p('*')] >> factor)
  23.                     | (root_node_d[ch_p('/')] >> factor));
  24.             exp    = term >> *((root_node_d[ch_p('+')] >> term)
  25.                     | (root_node_d[ch_p('-')] >> term));
  26.         }
  27.     };
  28. };
  29. //顯示AST的結構,Indent是縮進寬度
  30. typedef tree_match<char const*>::container_t container_t;
  31. void showTree(const container_t& con, int Indent)
  32. {
  33.     for(container_t::const_iterator itr=con.begin(); itr!=con.end(); ++itr)
  34.     {
  35.         //tree_node: value, children
  36.         //顯示當前值
  37.         cout << string(Indent*4, ' ') << "|--(" <<
  38.             string(itr->value.begin(), itr->value.end()) << ')' << endl;
  39.         //顯示子節點
  40.         showTree(itr->children, Indent+1);
  41.     }
  42. }
  43. int main()
  44. {
  45.     calculator calc;
  46.     const char *szExq = "12 * (24 - 15) / (17 + 6)";
  47.     tree_parse_info<> info = ast_parse(szExq, calc, space_p);
  48.     showTree(info.trees, 0);
  49.     return 0;
  50. }
    這個程序能夠顯示出整個 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的成員有:
  1. IteratorT   stop;
  2. bool        match;
  3. bool        full;
  4. std::size_t length;
  5. 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):
  1. typedef node_val_data_factory<double> factory_t;
  2. my_grammar gram;
  3. my_skip_grammar skip;
  4. tree_parse_info<iterator_t, factory_t> i =
  5.     ast_parse<factory_t>(first, last, gram, skip);
     rule有一個 id()方法能夠返回一個parser_id類型的標記,用它能夠區分各個不一樣的 rule,它返回什麼值由TagT模板參數決定,默認的parser_address_tag返回的是 rule的內存地址。
    咱們能夠用其它參數代替它以實現更適用的標記, Spirit已準備好的TagT策略有:
     parser_tag<N>,它接收一個整數,如
  1. rule<parser_tag > my_rule;
  2. assign(rule.id().to_long() == 123);
     dynamic_parser_tag, 它給 rule加入了 set_id(int)的能力,如:
  1. rule<dynamic_parser_tag> my_dynrule;
  2. my_dynrule.set_id(1234);    // set my_dynrule's id to 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
        忽略由它包裹的規則,好比例子中的:
  1. factor = str_real_p | inner_node_d[('(' >> exp >> ')')];
        也能夠這樣表示:
  1. factor = str_real_p | (no_node_d[ch_p('(')] >> exp >> no_node_d[ch_p(')')]);
     infix_node_d
        這個命令會刪除其規則匹配出的全部節點中偶數位置上的節點,好比:
  1. 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解釋器等,你們有興趣能夠去研究研究。
相關文章
相關標籤/搜索