boost::spirit::qi::repeatを用いて入力から読み取り回数を決定する

こんにちは.

今日は次のような構文をboost::spirit::qiで解析するコードが必要となったので少し書いてみました.

indexlength, data[1], data[2], ..., data[indexlength]

上記構文は第一項にデータの個数が出現し,それ以降はその個数分のデータがカンマ区切りで並ぶという形式です.
この形式の構文を解析するには

  • 第一項を先に読み込み,そのサイズ分バッファを確保してから残りを読み込む
  • 一度全ての入力をバッファに読み込んでから,第一項のサイズ分のデータを採用する
  • 独自の処理を記述する

などの対応も考えられますが,今回は出来る限りboost::spirit::qiだけを使って書きます.

#include <iostream>
#include <string>
#include <vector>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

using namespace boost;
namespace qi = boost::spirit::qi;

template< typename iterator >
struct material_list : public qi::grammar< iterator, int(), spirit::ascii::space_type >
{
	spirit::qi::rule< iterator, int(), spirit::ascii::space_type > expr, elem, factor;
	std::vector< int > buf;

	material_list() : material_list::base_type( expr )
	{
		int n = 0;

		expr = qi::int_[ phoenix::ref( n ) = qi::_1 ] 
			>> qi::repeat( phoenix::ref( n ) )[ elem ] >> qi::eoi;
		elem = "," >> factor[ phoenix::push_back( phoenix::ref( buf ), qi::_1 ) ];
		factor = qi::int_;
	}
};

記述の解説.

  • 入力に対する構文解析開始のルールをexprに設定しています
  • 整数型のnに第一項の数字を読み込み,繰り返し回数を決定しています(repeat)
  • elemでfactorで読み込まれた整数値をvector型であるbufにpush_backしています
  • qi::eoi(end of input)を用いることにより,入力の全てが文法に従っていることを解析の成功条件としています.

尚,上記コードにあるmaterial_listではgrammerが返す結果は使いません.下記は実際に上記記述を呼び出すコードです.

int main()
{
	material_list< std::string::iterator > ml;

	std::string str = "5,5,4,8,2,3";

	auto it = str.begin();
	int result = 0;

	if( spirit::qi::phrase_parse( it, str.end(), ml, spirit::ascii::space, result ) )
	{
		for( auto const i : ml.buf )
			std::cout << i << std::endl; //-> 5 4 8 2 3 (改行されて表示)
	}

	return 0;

}