昨天看到 王博煒 Blog中《五進位》這
篇文章。其中關於5進位到10進位的轉換自然沒有什麼意思,這篇文章給的代碼主要是討論如何進行運算式分析和計算的。作者自製了一個Stack,並且用其
形成了兩個堆棧分別用於儲存數值和運算子。比較典型的運算式處理的方法。從實現上看,代碼有些臃腫,而且必要的最佳化很少,另外就是沒有充分利用標準提供的
便利。比如那個Stack完全沒有必要自製,STL提供的std::stack<T>可以很好的完成任務。
而今天我要做的
是,使用boost::spirit來實現同樣的運算式分析和計算。眾所周知,boost是C++中品質很高的庫,被稱為準標準庫,因為其存在的一個很重
要的目的就是為下一代C++庫提供預案。目前已經有大量的boost庫成為了C++標準庫的一部分。我現在要用的是Boost的Spirit庫。這個庫可以直接在C++代碼中撰寫EBNF。學過編譯原理的朋友應該對此都很熟悉,這是一種比堆棧更靈活的解析運算式甚至程式的方式。
如果我們要處理四則運算的運算式,那麼我們只需要在C++中寫入下列EBNF的定義:
group = '(' >> expression >> ')';<br /> factor = integer | group;<br /> term = factor >> *(('*' >> factor) | ('/' >> factor));<br /> expression = term >> *(('+' >> term) | ('-' >> term));
我們就構成了這個運算式的格式定義,它可以很輕鬆的處理下列運算式的運算:
12345<br /> -12345<br /> +12345<br /> 1 + 2<br /> 1 * 2<br /> 1/2 + 3/4<br /> 1 + 2 + 3 + 4<br /> 1 * 2 * 3 * 4<br /> (1 + 2) * (3 + 4)<br /> (-1 + 2) * (3 + -4)<br /> 1 + ((6 * 200) - 20) / 6<br /> (1 + (2 + (3 + (4 + 5))))
很簡單吧?
使用過yacc或者*lex的朋友對這類定義肯定很熟悉。但是所不同的是,他們都是讓使用者寫一個模板,然後用yacc或者*lex處理模板產生相應語言的程式。程式臃腫且很難閱讀。而且由於不是自己寫的程式,調整起來總要經過一步手續,比較繁瑣。
而
使用C++的朋友則不用有這種煩惱,Boost的Spirit充分利用了C++強大的文法功能。我們可以直接在程式中寫入上述的運算式定義,然後我們的程
序就支援這些運算式的處理了。不需要任何額外的程式處理。所需要的僅僅是include一些標頭檔而已。是的,僅僅是include一些標頭檔。不要擔心
需要安裝什麼額外的東西,或者需要連結什麼庫,因為Spirit的實現完全是標頭檔組成的,我們不需要連結任何庫。把boost的標頭檔路徑放到編譯期
中,直接編譯就ok了。很輕巧。
下面就是我用Boost
Spirit實現的四則運算運算式的代碼,由於我的重點是運算式的解析和計算,因此我沒有特別處理五進位到十進位的轉換問題。但是添加起來顯然不麻煩。我
只給出了一個五進位整數部分的輸出。如果運算式出錯,可以直接用箭頭指出哪裡有錯。很方便調試:)
而且代碼量是原文章的五分之一。編譯後也僅僅是35KB,也不是很臃腫的。
大家多瞭解標準庫,多瞭解Boost,C++的編碼也是很有趣味的。
#include <boost/config/warning_disable.hpp><br />#include <boost/spirit/include/qi.hpp><br />#include <boost/spirit/include/phoenix_operator.hpp><br />#include <iostream><br />#include <string><br />#include <cmath><br />#include <limits><br />using namespace boost::spirit;<br />using namespace boost::spirit::qi;<br />using namespace boost::spirit::ascii;<br />using namespace boost::spirit::arg_names;<br />template <typename Iterator><br />struct calculator : grammar<Iterator, double(), space_type><br />{<br />calculator() : calculator::base_type(expression)<br />{<br />expression = term[_val = _1]<br />>> *( ('+' >> term[_val += _1]) | ('-' >> term[_val -= _1]) );<br />term = factor[_val = _1]<br />>> *( ('*' >> factor[_val *= _1]) | ('/' >> factor[_val /= _1]) );<br />factor = double_[_val = _1]| '(' >> expression[_val = _1] >> ')'<br />| ('-' >> factor[_val = -_1]) | ('+' >> factor[_val = _1]);<br />}<br />rule<Iterator, double(), space_type> expression, term, factor, number;<br />};<br />//http://www.jb.man.ac.uk/~slowe/cpp/itoa.html<br />std::string itoa(int value, int base) {<br />const int MAX_DIGITS = 35;<br />const char* DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz";<br />std::string buf;<br />buf.reserve( MAX_DIGITS ); // Pre-allocate enough space.<br />if (base < 2 || base > 36) return buf;<br />int quotient = value;<br />do {<br />buf.push_back(DIGITS[ std::abs(quotient % base) ]);<br />quotient /= base;<br />} while ( quotient );<br />if ( value < 0) buf.push_back('-');<br />std::reverse( buf.begin(), buf.end() );<br />return buf;<br />}<br />int main(int argc, char* argv[])<br />{<br />std::cout << "請輸入一個運算式,如:3+2.5*(6-25/4)-8.32" << std::endl << std::endl;<br />std::cout << "或輸入q退出。" << std::endl << std::endl;<br />std::cout << "> ";<br />calculator<std::string::const_iterator> calc;<br />std::string str;<br />double result;<br />while (std::getline(std::cin, str))<br />{<br />if (str.empty() || str[0] == 'q' || str[0] == 'Q')<br />break;<br />std::string::const_iterator iter = str.begin();<br />std::string::const_iterator end = str.end();<br />bool r = phrase_parse(iter, end, calc, result, space);<br />if (r && iter == end)<br />{<br />std::cout << "輸入文法正確,運算式的值為:";<br />if (result == std::numeric_limits<double>::infinity())<br />std::cout << "∞";<br />else if (result == std::numeric_limits<double>::quiet_NaN())<br />std::cout << "結果非數值";<br />else<br />{<br />std::cout << result << std::endl;<br />std::cout << "整數部分轉換為5進位為:" << itoa(static_cast<int>(result), 5);<br />}<br />std::cout << std::endl;<br />}<br />else<br />{<br />std::cout << "[輸入的運算式錯誤]" << std::endl;<br />std::cout << str << std::endl;<br />std::cout << std::string(iter - str.begin(), '-') << "^" << std::endl;<br />}<br />std::cout << std::endl << "> ";<br />}<br />return 0;<br />}
運行結果如下:
請輸入一個運算式,如:3+2.5*(6-25/4)-8.32<br />或輸入q退出。<br />> 3+2.5*(6-25/4)-8.32<br />輸入文法正確,運算式的值為:-5.945<br />整數部分轉換為5進位為:-10<br />> -6<br />輸入文法正確,運算式的值為:-6<br />整數部分轉換為5進位為:-11<br />> 6<br />輸入文法正確,運算式的值為:6<br />整數部分轉換為5進位為:11<br />> 1/0<br />輸入文法正確,運算式的值為:∞<br />> 23 + 4 ((5)-3* 6) + (-1)<br />[輸入的運算式錯誤]<br />23 + 4 ((5)-3* 6) + (-1)<br />-------^<br />> 23 + 4 ( ( 5-3*6) +1)<br />[輸入的運算式錯誤]<br />23 + 4 ( ( 5-3*6) +1)<br />-------^<br />> 23 + 4 + ( -5 *3)<br />輸入文法正確,運算式的值為:12<br />整數部分轉換為5進位為:22<br />>