C ++でiniファイル用のパーサーを作成する

この記事では、パーサーのiniファイルをC ++で記述する方法を説明します。 前回の記事で構築た文脈自由文法を基礎として取り上げます。 パーサーを構築するには、 Boost Spiritライブラリを使用します。これにより、 パーサーコンビネーターを使用して既製のプリミティブパーサーを組み合わせて独自のパーサーを構築できます。



重要:この記事は、読者が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 ++ブログへの移植にご協力いただきありがとうございます。



All Articles