boost::spirit::x3 Unicode対応メモ


探したけど、x3版の対応方法は出てこなかったのでメモ。

はじめに

boost::spiritは、特に指定しなければx3::standardに定義された文字エンコードが使われる。

char.hpp
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;

これは、次の通りの定義になっている。

standard.hpp
    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の定義は次の通り

unicode.hpp
    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を保証する型なのかは調べてないんですが、どうなんですかね…。詳しい人教えて下さい)

参考