重要:この記事は、読者がC ++の基本(STLの使用を含む)に精通していることを前提としています。 自分に自信がない場合は、C ++およびSTLの初心者向けの記事を最初に読むことをお勧めします。
文法
最初に、前の記事で作成したiniファイルの文法を思い出しましょう。
inidata = spaces, {section} . 
      
        
        
        
      
     section = "[", ident, "]", stringSpaces, "\n", {entry} . 
      
        
        
        
      
     entry = ident, stringSpaces, "=", stringSpaces, value, "\n", spaces . 
      
        
        
        
      
     ident = identChar, {identChar} . 
      
        
        
        
      
     identChar = letter | digit | "_" | "." | "," | ":" | "(" | ")" | "{" | "}" | "-" | "#" | "@" | "&" | "*" | "|" . 
      
        
        
        
      
     value = {not "\n"} . 
      
        
        
        
      
     stringSpaces = {" " | "\t"} . 
      
        
        
        
      
     spaces = {" " | "\t" | "\n" | "\r"} . 
      
        
        
        
      
    
      
      彼女の説明がすぐに必要です。
C ++およびBoost Spirit
ブーストをインストールすることから始めます(公式Webサイトで入手するか、OSの既製のパッケージを探してください)。 すべてのスピリットはヘッダーにあるため、ブーストを収集する必要はありません。 異なるシステムのインストールプロセスは異なる場合があるため、ここでは説明しません。
C ++でパーサーを作成するプロセスを詳細に説明しようとします。 ただし、この記事の目的ではないため、パフォーマンスについては特に考えません。
必要なヘッダーを接続することから始めましょう。
1 #include <fstream> 
      
        
        
        
      
     2 #include <functional> 
      
        
        
        
      
     3 #include <numeric> 
      
        
        
        
      
     4 #include <list> 
      
        
        
        
      
     5 #include <vector> 
      
        
        
        
      
     6 #include <string> 
      
        
        
        
      
     7 
      
        
        
        
      
     8 #include <boost/spirit.hpp> 
      
        
        
        
      
     9 #include <boost/algorithm/string.hpp> 
      
        
        
        
      
     10 
      
        
        
        
      
     11 using namespace std; 
      
        
        
        
      
     12 using namespace boost::spirit; 
      
        
        
        
      
    
      
      Spirit自体のヘッダーに加えて、boostのラインアルゴリズムのライブラリを含めました(trim関数を使用します)。 名前空間の構成の使用は常に良い習慣とは限りませんが、ここでは簡潔にするために許可します。
データタイプを定義します。レコードはキーと値のペア、セクションはレコードのキーリスト、すべてのiniファイルデータはセクションのリストです。
14 typedef pair<string, string>    Entry; 
      
        
        
        
      
     15 typedef list<Entry >            Entries; 
      
        
        
        
      
     16 typedef pair<string, Entries>  Section; 
      
        
        
        
      
     17 typedef list<Section>          IniData; 
      
        
        
        
      
    
      
      データ型に加えて、パーサーが次の非ターミナルを解析するときに呼び出されるイベントハンドラーが必要です。
19 struct add_section 
      
        
        
        
      
     20 { 
      
        
        
        
      
     21   add_section( IniData & data ) : data_(data) {} 
      
        
        
        
      
     22 
      
        
        
        
      
     23 void operator ()( char const * p, char const * q) const 
      
        
        
        
      
     24   { 
      
        
        
        
      
     25       string s(p,q); 
      
        
        
        
      
     26       boost::algorithm::trim(s); 
      
        
        
        
      
     27       data_.push_back( Section( s, Entries() ) ); 
      
        
        
        
      
     28   } 
      
        
        
        
      
     29 
      
        
        
        
      
     30   IniData & data_; 
      
        
        
        
      
     31 }; 
      
        
        
        
      
     32 
      
        
        
        
      
     33 struct add_key 
      
        
        
        
      
     34 { 
      
        
        
        
      
     35   add_key( IniData & data ) : data_(data) {} 
      
        
        
        
      
     36 
      
        
        
        
      
     37 void operator ()( char const * p, char const * q) const 
      
        
        
        
      
     38   { 
      
        
        
        
      
     39       string s(p,q); 
      
        
        
        
      
     40       boost::algorithm::trim(s); 
      
        
        
        
      
     41       data_.back().second.push_back( Entry( s, string() ) ); 
      
        
        
        
      
     42   } 
      
        
        
        
      
     43 
      
        
        
        
      
     44   IniData & data_; 
      
        
        
        
      
     45 }; 
      
        
        
        
      
     46 
      
        
        
        
      
     47 struct add_value 
      
        
        
        
      
     48 { 
      
        
        
        
      
     49   add_value( IniData & data ) : data_(data) {} 
      
        
        
        
      
     50 
      
        
        
        
      
     51 void operator ()( char const * p, char const * q) const 
      
        
        
        
      
     52   { 
      
        
        
        
      
     53       data_.back().second.back().second.assign(p, q); 
      
        
        
        
      
     54   } 
      
        
        
        
      
     55 
      
        
        
        
      
     56   IniData & data_; 
      
        
        
        
      
     57 }; 
      
        
        
        
      
    
      
      イベントハンドラーは、入力として(2つのポインターを介して)文字列の一部を受け取るファンクターです。
パーサーが次のセクションを認識すると、add_sectionファンクターが呼び出されます。 Add_sectionは、このセクションの名前をパラメーターとして取得します。 パーサーが新しいパラメーターの名前を認識すると、add_keyファンクターが呼び出されます。 パーサーがパラメーターの値を認識すると、add_valueファンクターが呼び出されます。 これらのファンクターを使用して、IniDataの順次入力が編成されます。最初に空のセクション(add_section)が追加され、次に空の値を持つエントリ(add_key)がこのセクションに入れられ、この値が入力されます(add_value)。
次に、文法をBackus-Naur表記からC ++に転送します。 このために、特別なクラスinidata_parserが作成されます。
59 struct inidata_parser : public grammar<inidata_parser> 
      
        
        
        
      
     60 { 
      
        
        
        
      
     61   inidata_parser(IniData & data) : data_(data) {} 
      
        
        
        
      
     62 
      
        
        
        
      
     63 template < typename ScannerT> 
      
        
        
        
      
     64 struct definition 
      
        
        
        
      
     65   { 
      
        
        
        
      
     66       rule<ScannerT> inidata, section, entry, ident, value, stringSpaces, spaces; 
      
        
        
        
      
     67 
      
        
        
        
      
     68       rule<ScannerT> const & start() const { return inidata; } 
      
        
        
        
      
     69 
      
        
        
        
      
     70       definition(inidata_parser const & self) 
      
        
        
        
      
     71       { 
      
        
        
        
      
     72         inidata = *section; 
      
        
        
        
      
     73 
      
        
        
        
      
     74         section = ch_p( '[' ) 
      
        
        
        
      
     75               >> ident[add_section(self.data_)] 
      
        
        
        
      
     76               >> ch_p( ']' ) 
      
        
        
        
      
     77               >> stringSpaces 
      
        
        
        
      
     78               >> ch_p( '\n' ) 
      
        
        
        
      
     79               >> spaces 
      
        
        
        
      
     80               >> *(entry); 
      
        
        
        
      
     81 
      
        
        
        
      
     82         entry =  ident[add_key(self.data_)] 
      
        
        
        
      
     83               >> stringSpaces 
      
        
        
        
      
     84               >> ch_p( '=' ) 
      
        
        
        
      
     85               >> stringSpaces 
      
        
        
        
      
     86               >> value[add_value(self.data_)] 
      
        
        
        
      
     87               >> spaces; 
      
        
        
        
      
     88 
      
        
        
        
      
     89 
      
        
        
        
      
     90         ident  = +(alnum_p | chset<>( "-_.,:(){}#@&*|" ) ); 
      
        
        
        
      
     91 
      
        
        
        
      
     92         value = *(~ch_p( '\n' )); 
      
        
        
        
      
     93 
      
        
        
        
      
     94         stringSpaces = *blank_p; 
      
        
        
        
      
     95 
      
        
        
        
      
     96         spaces = *space_p; 
      
        
        
        
      
     97       } 
      
        
        
        
      
     98 
      
        
        
        
      
     99   }; 
      
        
        
        
      
     100 
      
        
        
        
      
     101   IniData & data_; 
      
        
        
        
      
     102 }; 
      
        
        
        
      
    
      
      このクラスは、文法全体をカプセル化します。 さらに詳しく調べます。 59行目では、パーサーがcrtpを使用して文法テンプレートクラスを継承していることがわかります。これは、Spiritが正しく機能するために必要です。 パーサーは、コンストラクターで空のIniDataへのリンクを取得して保存します(61)。 パーサー内で、テンプレート構造定義(63-64)を定義する必要があります。 定義構造には、ルールタイプのデータメンバーがあります。これらは、Backus-Naur(66)の形式の文法の各非終端記号のパーサーです。 メインの非端末-inidata(68)へのリンクを返す開始メンバー関数を定義する必要があります。
定義コンストラクターでは、文法を記述します。 文法はC ++でほぼ一字一句書き直されます。 inidataはいくつかのセクションで構成されます(72)-これはアスタリスクで表されます(Kleeneクロージャと同様ですが、左側にアスタリスクが付きます)。 セクションは角かっこで始まります。このために、組み込みのch_pパーサーが使用され、1文字が解析されます。 Backus-Naur表記のコンマの代わりに、>>演算子が使用されます。 式の後の角括弧内に、イベントハンドラファンクタが記述されます(75、82、86)。 左側の「+」記号は「少なくとも1つ」を意味し、「〜」は否定を意味します。 alnum_p-文字と数字の組み込みパーサー。 chset <>は、文字列の任意の文字に一致します(マイナス記号が最初に来ることが重要です。それ以外の場合は、「az」のように間隔文字として認識されます)。 blank_pは行の空白(スペースまたはタブ)に一致し、space_pは空白(ラインフィードとキャリッジリターンを含む)に一致します。
identとidentCharの非端末は、「+」演算子のおかげで1つに統合されていることに注意してください。これは、Backus-Naur表記では不可能だったためです。 そのような指定はありません。
それはすべて文法です。 コメントを削除し、IniDataで値を探す方法を学ぶことは残っています。
コメントを削除するには、特別なファンクターが必要です。
104 struct is_comment{ bool operator ()( string const & s ) const { return s[ 0 ] == '\n' || s[ 0 ] == ';' ; } }; 
      
        
        
        
      
    
      
      次に、IniDataで検索関数を作成しましょう。
106 struct first_is 
      
        
        
        
      
     107 { 
      
        
        
        
      
     108     first_is(std::string const & s) : s_(s) {} 
      
        
        
        
      
     109 
      
        
        
        
      
     110 template < class Pair > 
      
        
        
        
      
     111 bool operator ()(Pair const & p) const { return p.first == s_; } 
      
        
        
        
      
     112 
      
        
        
        
      
     113     string const & s_; 
      
        
        
        
      
     114 }; 
      
        
        
        
      
     115 
      
        
        
        
      
     116 bool find_value( IniData const & ini, string const & s, string const & p, string & res ) 
      
        
        
        
      
     117 { 
      
        
        
        
      
     118     IniData::const_iterator sit = find_if(ini.begin(), ini.end(), first_is(s)); 
      
        
        
        
      
     119 if (sit == ini.end()) 
      
        
        
        
      
     120 return false ; 
      
        
        
        
      
     121 
      
        
        
        
      
     122     Entries::const_iterator it = find_if(sit->second.begin(), sit->second.end(), first_is(p)); 
      
        
        
        
      
     123 if (it == sit->second.end()) 
      
        
        
        
      
     124 return false ; 
      
        
        
        
      
     125 
      
        
        
        
      
     126     res = it->second; 
      
        
        
        
      
     127 return true ; 
      
        
        
        
      
     128 } 
      
        
        
        
      
    
      
      first_isファンクターの代わりに、boost :: bindを適用できますが、1つのヒープ内のすべてに干渉しないことにしました。 検索はすべて簡単です。最初に、名前でセクションを検索し、次にセクションレコードのリストで名前でパラメーターを検索し、すべてが見つかった場合、linkパラメーターを通じてパラメーターの値を返します。
mainを書くことは残っています。
130 int main( int argc, char ** argv) 
      
        
        
        
      
     131 { 
      
        
        
        
      
     132 if ( argc != 4 ) 
      
        
        
        
      
     133   { 
      
        
        
        
      
     134       cout << "Usage: " << argv[ 0 ] << " <file.ini> <section> <parameter>" << endl; 
      
        
        
        
      
     135 return 0 ; 
      
        
        
        
      
     136   } 
      
        
        
        
      
     137 
      
        
        
        
      
     138   ifstream in(argv[ 1 ]); 
      
        
        
        
      
     139 if ( !in ) 
      
        
        
        
      
     140   { 
      
        
        
        
      
     141       cout << "Can't open file \" " << argv[ 1 ] << '\"' << endl; 
      
        
        
        
      
     142 return 1 ; 
      
        
        
        
      
     143   } 
      
        
        
        
      
     144 
      
        
        
        
      
     145   vector< string > lns; 
      
        
        
        
      
     146 
      
        
        
        
      
     147   std::string s; 
      
        
        
        
      
     148 while ( !in.eof() ) 
      
        
        
        
      
     149   { 
      
        
        
        
      
     150       std::getline( in, s ); 
      
        
        
        
      
     151       boost::algorithm::trim(s); 
      
        
        
        
      
     152       lns.push_back( s+= '\n' ); 
      
        
        
        
      
     153   } 
      
        
        
        
      
     154   lns.erase( remove_if(lns.begin(), lns.end(), is_comment()), lns.end()); 
      
        
        
        
      
     155   string text = accumulate( lns.begin(), lns.end(), string() ); 
      
        
        
        
      
     156 
      
        
        
        
      
     157   IniData data; 
      
        
        
        
      
     158   inidata_parser parser(data); //  Our parser 
      
        
        
        
      
     159   BOOST_SPIRIT_DEBUG_NODE(parser); 
      
        
        
        
      
     160 
      
        
        
        
      
     161   parse_info<> info = parse(text.c_str(), parser, nothing_p); 
      
        
        
        
      
     162 if (!info.hit) 
      
        
        
        
      
     163   { 
      
        
        
        
      
     164       cout << "Parse error \n " ; 
      
        
        
        
      
     165 return 1 ; 
      
        
        
        
      
     166   } 
      
        
        
        
      
     167 
      
        
        
        
      
     168   string res; 
      
        
        
        
      
     169 if (find_value(data, argv[ 2 ], argv[ 3 ], res)) 
      
        
        
        
      
     170       cout << res; 
      
        
        
        
      
     171 else 
      
        
        
        
      
     172       cout << "Can't find requested parameter" ; 
      
        
        
        
      
     173   cout << endl; 
      
        
        
        
      
     174 } 
      
        
        
        
      
    
      
      132〜136行目-プログラムパラメータを確認します。4つがない場合は、使用状況を表示します。 パラメーターがすべて問題ない場合は、ファイルを開きます(138-143)。 ファイルに問題がなければ、lns行の配列(145)を作成し、ファイル全体(147-153)を読み取ります。 その後、is_comment(154)格納ファンクターを使用して、そこからコメントを削除します。 結論として、すべての行を1つ(155)に接着します。
157-159行目では、パーサーが作成および初期化されます。 次に、パーサーを起動します。これには、テキスト自体、パーサー、およびスキップされた文字用の特別なパーサーを使用する解析関数を使用します(たとえば、すべてのスペースをスキップします)。 この場合、スキップされた文字のパーサーは空になります-nothing_p(つまり、解析なし)。 解析関数の結果は、parse_info <>構造です。 この構造のブールフィールドヒットに興味があります。エラーが発生していない場合はtrueです。 行162〜166では、エラーが発生したかどうかを報告します。 コマンドラインで指定されたパラメーターを見つけ、その値(168-173)を表示するためだけに残ります。
これで、コードは完全に作成されました。 コンパイルして、テスト例で実行します。
$ g++ ini.cpp -o ini_cpp 
      
        
        
        
      
     
      
        
        
        
      
     $ ./ini_cpp /usr/lib/firefox-3.0.5/application.ini App ID 
      
        
        
        
      
     {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 
      
        
        
        
      
     
      
        
        
        
      
     $ ./ini_cpp /usr/lib/firefox-3.0.5/application.ini App IDD 
      
        
        
        
      
     Can't find requested parameter 
      
        
        
        
      
    
      
      この記事が、独自のパーサーの作成に役立つことを願っています=)
興味深いメモ:この記事のパーサーと、 「Haskellのiniファイル用のパーサーの作成」の Haskellのパーサーを比較できます 。
PS。 この記事のC ++ブログへの移植にご協力いただきありがとうございます。