読者です 読者をやめる 読者になる 読者になる

凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

変態と名高い Boost.Spirit.Qi を使って Julius の設定ファイルもどきを自動生成してみ…てる途中

C++ Julius

はじめに

リモコンはオワコン。音声認識でお部屋の家電を操作してみた。 - 凹みTips の設定ファイルたちの記述を楽にしようと現在取り組んでいます。
現在は、IR 信号と言葉の対応付け用の XML、音声解析エンジンの Julius の設定ファイル(grammar ファイルと voca ファイル)の計 3 ファイルもいじらないと新しいコマンドを付け加えることが出来ません。
そこで XML から自動的に grammar と voca を生成できないかと思っています。
今のところの最終イメージとしては、「電気(を|)(つけて|オン)」みたいな記述から「電気をつけて」「電気オン」といった文章をババッと作成して、それを MeCab で解析、文節に区切ってうまーい具合に sp (空白)をはさみつつ、grammar と voca を自動的に生成してくれないかな、と思ってます。
voca ファイルの生成については前回のエントリ(ICU でカタカナ → ローマ字変換をしてみた - 凹みTips)を利用して MeCab から取り出した読み(カタカナ)をローマ字に変換してやろうかなー、と考えています。

本エントリでは、この「電気(を|)(つけて|オン)」みたいなところから grammar と voca っぽいフォーマットを作るところまでやってみた内容を報告します。
パースには変態と名高い Boost.Spirit.Qi さんを勉強して使ってみました。
ひたすら tutorial(Spirit 2.5.1 - 1.48.0)読んで、booooooost!! するコンパイル時間とエラーに魂気を吸い取られながらも何とか出来ました。

環境

  • Ubuntu 10.04
  • Boost 1.48.0
  • gcc version 4.6.1 20110617 (prerelease) (GCC)

ソースコード

#include <iostream>
#include <string>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>

#include <boost/format.hpp>

namespace qi    = boost::spirit::qi;
namespace phx   = boost::phoenix;
namespace sw    = qi::standard_wide;

// 結果を格納する型(2次元 string テーブル)
typedef std::vector<std::vector<std::string>> str_2d_array;

/* ------------------------------------------------------------------------- */
//  Parser
//    string: "(a|b|c)d(e|f)g" -->  str_2d_array: { {a,b,c}, d, {e,f}, g}
/* ------------------------------------------------------------------------- */
template <typename Iterator>
bool command_parse(Iterator first, Iterator last, str_2d_array& result)
{
	using sw::char_;
	using qi::_1;
	using qi::lit;

	qi::rule<Iterator, std::string(), sw::space_type> word, words, sentence;
	std::vector<std::string> buf;

	word     = +( char_ - '(' - '|' - ')' );
	words    = -lit('(')
	            >> ( word [phx::push_back(phx::ref(buf), _1)] % '|' )
	            >> -lit("|)") [phx::push_back(phx::ref(buf), "")]
	            >> -lit(')');
	sentence = *(
	              words
	                [phx::push_back(phx::ref(result), phx::ref(buf))]
	                [phx::clear(phx::ref(buf))]
	            )
	            >> qi::eol;

	bool r = qi::phrase_parse(first, last, sentence, sw::space);

	if (!r || first != last) {
		return false;
	}

	return true;
}

/* ------------------------------------------------------------------------- */
//  main 関数(テスト用)
/* ------------------------------------------------------------------------- */
int main(int argc, char const* argv[])
{
	std::string str = "(電気|ライト)(を|)(オン|オフ|つけて|消して)";
	std::string::const_iterator iter = str.begin(), end = str.end();

	str_2d_array sentences;
	if (command_parse(iter, end, sentences)) {
		std::cout << "failed" << std::endl;
		return 1;
	}

	std::cout << "kaden.grammar" << std::endl;
	std::cout << "S\t: NS_B ";
	for (size_t i = 0; i < sentences.size(); ++i) {
		std::cout << boost::format("WORD%1% ") % i;
	}
	std::cout << "NS_E\n\n";

	std::cout << "kaden.voca" << std::endl;
	int n = 0;
	for (const auto& sentence : sentences) {
		std::cout << boost::format("%% WORD%1%\n") % n++;
		for (const auto& x : sentence) {
			std::cout << boost::format("%1%\t%2%\n") % ( (x=="") ? "<sp>" : x ) % "d u m m y";
		}
	}
	std::cout <<
		"% NS_B\n"
		"<s>\tsilB\n"
		"% NS_E\n"
		"<s>\tsilE\n";

	return 0;
}

(' - '|' - ')とかなんか可愛い。
基本的にはセマンティックアクション使ってゴリゴリとコンテナに push_back してる感じです。
qi::standard_wide::char_ とか qi::standard_wide::space を使ってあげれば日本語もパースできます。
いやしかし、もう C++ じゃないですね…。すごい。

コンパイル

g++-4.6 hoge.cpp -std=c++0x

結果

kaden.grammar
S       : NS_B WORD0 WORD1 WORD2 NS_E

kaden.voca
% WORD0
電気    d u m m y
ライト  d u m m y
% WORD1
を      d u m m y
<sp>    d u m m y
% WORD2
オン    d u m m y
オフ    d u m m y
つけて  d u m m y
消して  d u m m y
% NS_B
<s>     silB
% NS_E
<s>     silE

次回

次は MeCab と繋ぎこみたい。