探したけど、x3版の対応方法は出てこなかったのでメモ。
はじめに
boost::spiritは、特に指定しなければx3::standardに定義された文字エンコードが使われる。
namespace standard
{
typedef any_char<char_encoding::standard> char_type;
constexpr auto char_ = char_type{};
constexpr literal_char<char_encoding::standard, unused_type>
lit(char ch)
{
return { ch };
}
constexpr literal_char<char_encoding::standard, unused_type>
lit(wchar_t ch)
{
return { ch };
}
}
//ここで、boost::spirit::x3 (標準の名前空間)に展開される。
using standard::char_type;
using standard::char_;
using standard::lit;
これは、次の通りの定義になっている。
struct standard
{
//エンコードに使われるchar型
typedef char char_type;
typedef unsigned char classify_type;
// ------中略----------
// 文字判定部分
static bool
ischar(int ch)
{
// uses all 8 bits
// we have to watch out for sign extensions
return (0 == (ch & ~0xff) || ~0 == (ch | 0xff)) != 0;
}
//--------中略----------
};
C++におけるchar型はUnicodeに対応しておらず、
判定部分でも、0xffを超えるコードポイントの文字は範囲外とされてしまう(文字と判定されない)。
これは日本語や、中国語などの一部外国語がパースできないことを指し、特に文字列をデータとしてハードコードするDSLを作成する場合、ローカライズ対応する際に非常に困る。
そこで、boost::spiritにはunicode対応の文字セットが用意されている。
方法
利用方法はシンプルで、BOOST_SPIRIT_X3_UNICODE
マクロを定義してやるだけで良い。
//unicode対応マクロ
#define BOOST_SPIRIT_X3_UNICODE
//対応マクロ宣言のあとに、ヘッダを読む
#include <boost/spirit/home/x3.hpp>
namespace parser
{
using x3::unicode::alpha;
using x3::unicode::alnum;
using x3::unicode::char_;
//識別子のパース
auto const identifier_def
= raw[lexeme[(alpha | '_') >> *(alnum | '_')]];
//文字列のパース
auto const string_literal_def = '"' >> lexeme[*(~char_('"'))] >> '"';
}
※前バージョンのboost::spirit::qiを使う場合は、BOOST_SPIRIT_UNICODE
マクロになる。
unicodeの定義は次の通り
struct unicode
{
typedef ::boost::uint32_t char_type;
typedef ::boost::uint32_t classify_type;
static bool
ischar(char_type ch)
{
// unicode code points in the range 0x00 to 0x10FFFF
return ch <= 0x10FFFF;
}
};
文字判定の部分の範囲が0x10FFFF(unicodeの最大範囲)まで広がっており、char_typeも32bitに広がっていることがわかる。
(boost::uint32_tとやらが、UTF-32を保証する型なのかは調べてないんですが、どうなんですかね…。詳しい人教えて下さい)