Основное правило: Правило делает (а) межсетевой экран компиляции (б) приведение атрибутов. Если вам нужно, используйте правило.

чаю Boost Spirit, написав анализатор, который анализирует два варианта шестнадцатеричного числа, используемого NAMS:

Шестнадцатеричное число с любым суффиксом0x/0h или префиксh/x.Шестнадцатеричный номер с префиксом$ и должен сопровождатьсядесятичный цифра.

Вот то, что я придумал до сих пор и сColiru Session:

//#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/spirit/include/support_extended_variant.hpp>

namespace x3 = boost::spirit::x3;

namespace ast {
    struct hex_data : std::string {};
    struct pascal_hex_data : std::string {};

    struct declared_data : boost::spirit::extended_variant<hex_data, pascal_hex_data>
    {
        declared_data () : base_type ()                              { std::cout << "ctor default\n";               } 
        declared_data (hex_data const& rhs) : base_type (rhs)        { std::cout << "ctor hex: " << rhs << "\n";    } 
        declared_data (pascal_hex_data const& rhs) : base_type (rhs) { std::cout << "ctor pascal: " << rhs << "\n"; } 
    };

} // namespace ast

typedef x3::rule<struct hex_digits_class,     std::string>          hex_digit_type;
typedef x3::rule<struct hex_data_class,       ast::hex_data>        hex_data_type;
typedef x3::rule<struct pascalhex_data_class, ast::pascal_hex_data> pascalhex_data_type;
typedef x3::rule<struct declared_data_class,  ast::declared_data>   declared_data_type;

const hex_data_type       hex_data       = "hex_data";
const hex_digit_type      hex_digit      = "hex_digit";
const pascalhex_data_type pascalhex_data = "pascal_hex_data";
const declared_data_type  declared_data  = "declared_data";

auto const hex_digit_def =
  = x3::skip(x3::char_('_'))
      [
        x3::no_case
        [
          x3::char_ ('0', '9') | x3::char_ ("a", "f")
        ]
      ]
  ;

auto const hex_data_def 
  = x3::no_case[x3::lit ("0h") | "0x"] >> +hex_digit_def
  | +hex_digit_def >> x3::no_case[x3::lit ("h") | "x"]
  ;

auto const pascalhex_data_def 
  = x3::lit ("$") >> x3::char_ ('0', '9') >> +hex_digit_def;

auto const declared_data_def 
  = hex_data_def
  | pascalhex_data_def
  ;

BOOST_SPIRIT_DEFINE (hex_digit, hex_data, pascalhex_data, declared_data)

struct Visitor
{
    using result_type = std::string;
    std::string operator()(ast::hex_data const & v) const        { return "hex_data";        } 
    std::string operator()(ast::pascal_hex_data const & v) const { return "pascal_hex_data"; } 
};

int main()
{
  std::string input = "$9";
  ast::declared_data parsed;

  bool r =
    x3::parse (input.begin (), input.end (),
               declared_data_def,
               parsed);

  std::cout << "r = " << r << "\n";
  Visitor v;
  std::cout << "result = " << boost::apply_visitor(v, parsed) << "\n";
}

Тем не менее, правилоpascalhex_data_def не компилируется с сообщением об ошибке, которое выглядит так, как будто дух выводит атрибут правила как фьюжн-кортежchar а такжеvector изvariant даже если указано, что правило имеет атрибут ast, полученный изstring:

typedef x3::rule<struct pascalhex_data_class, ast::pascal_hex_data> pascalhex_data_type;

Кто-нибудь может указать, почему атрибут, определяемый boost, не соответствует указанному? Во всяком случае, чтобы заставить правило генерировать строку, а неtuple буст пытается вернуться?

Ответы на вопрос(1)

Решение Вопроса

чего он достигает. Однако, посмотрев на это в течение значительного времени, я заметил, что вы объявляете правила (которые приводят их типы атрибутов), но не используете их в решающий момент:

auto const declared_data_def = hex_data_def | pascalhex_data_def;

Это означает, что вы напрямую строите дерево выражений из шаблона выражения (_def) инициализаторы, а не правила:

auto const declared_data_def = hex_data | pascalhex_data;

Это компилируется. Это все еще оставляет довольно много проблем:

Вы можете / должны обойтись без конструкторов вариантов:

struct declared_data : boost::spirit::extended_variant<hex_data, pascal_hex_data> {
    using extended_variant::extended_variant;
};

Ты можешь написатьx3::char_ ('0', '9') какx3::char_("0-9")так что вы можете написать

x3::no_case
[
    x3::char_ ('0', '9') | x3::char_ ("a", "f")
]

вместо того, чтобы

x3::no_case [ x3::char_ ("0-9a-f") ]

или даже

x3::char_ ("0-9a-fA-F")

или, может быть, просто:

x3::xdigit

hex_digits_type объявляетstd::string атрибут, но анализирует только один символ. Вместо того, чтобы использовать+hex_digits_def, просто используйтеhex_digits и писать:

auto const hex_digits_def = x3::skip(x3::char_('_')) [ +x3::xdigit ];

ваше определение

"$" >> x3::char_("0-9") >> hex_digits

потребляет первую цифру шестнадцатеричного числа. Это приводит к ошибке (парсинг пустой строки, например,$9). Вместо этого вы, вероятно, хотите проверить сoperator&:

'

или действительно:

'

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: 'Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex | x3::hex >> hex_qualifier ; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; auto f = input.begin(), l = input.end(); bool r = parse(f, l, parser::hex_literal, parsed) && f==l; std::cout << std::boolalpha << "r = " << r << ",\tresult = " << parsed << ",\tremaining: '" << std::string(f,l) << "'\n"; } }

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit | '0' >> qualifier) >> digits_ | digits_ >> qualifier ; if (x3::parse(f, l, forms, digits)) { attr = std::stoull(digits, nullptr, 16); return true; } return false; } }; hex_literal_type static const hex_literal; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex", // edge cases "ffffffffH", // fits "1ffffffffH", // too big "$00_00___01___________0__________0", // fine "0x", // fine, same as "0h" "$", // upper case "$9", "0X1B", "0H1C", "1DH", "1EX", "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX", }) { ast::hex_literal parsed = 0; auto f = input.begin(), l = input.end(); bool r = parse(f, l, parser::hex_literal, parsed) && f==l; std::cout << std::boolalpha << "r = " << r << ",\tresult = " << parsed << ",\tremaining: '" << std::string(f,l) << "'\n"; } }

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex | x3::hex >> hex_qualifier ; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; auto f = input.begin(), l = input.end(); bool r = parse(f, l, parser::hex_literal, parsed) && f==l; std::cout << std::boolalpha << "r = " << r << ",\tresult = " << parsed << ",\tremaining: '" << std::string(f,l) << "'\n"; } }

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

r = true, result = 9, remaining: '' r = true, result = 27, remaining: '' r = true, result = 28, remaining: '' r = true, result = 29, remaining: '' r = true, result = 30, remaining: '' r = true, result = 159, remaining: '' r = true, result = 507, remaining: '' r = true, result = 508, remaining: '' r = true, result = 509, remaining: '' r = true, result = 510, remaining: '' Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex | x3::hex >> hex_qualifier ; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; auto f = input.begin(), l = input.end(); bool r = parse(f, l, parser::hex_literal, parsed) && f==l; std::cout << std::boolalpha << "r = " << r << ",\tresult = " << parsed << ",\tremaining: '" << std::string(f,l) << "'\n"; } }

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit | '0' >> qualifier) >> digits_ | digits_ >> qualifier ; if (x3::parse(f, l, forms, digits)) { attr = std::stoull(digits, nullptr, 16); return true; } return false; } }; hex_literal_type static const hex_literal; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex", // edge cases "ffffffffH", // fits "1ffffffffH", // too big "$00_00___01___________0__________0", // fine "0x", // fine, same as "0h" "$", // upper case "$9", "0X1B", "0H1C", "1DH", "1EX", "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX", }) { ast::hex_literal parsed = 0; auto f = input.begin(), l = input.end(); bool r = parse(f, l, parser::hex_literal, parsed) && f==l; std::cout << std::boolalpha << "r = " << r << ",\tresult = " << parsed << ",\tremaining: '" << std::string(f,l) << "'\n"; } }

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex | x3::hex >> hex_qualifier ; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; auto f = input.begin(), l = input.end(); bool r = parse(f, l, parser::hex_literal, parsed) && f==l; std::cout << std::boolalpha << "r = " << r << ",\tresult = " << parsed << ",\tremaining: '" << std::string(f,l) << "'\n"; } }

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit | '0' >> qualifier) >> digits_ | digits_ >> qualifier ; if (x3::parse(f, l, forms, digits)) { attr.value = std::stoull(digits, nullptr, 16); attr.source.assign(b,l); return true; } return false; } }; hex_literal_type static const hex_literal; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex", // edge cases "ffffffffH", // fits "1ffffffffH", // too big "$00_00___01___________0__________0", // fine "0x", // fine, same as "0h" "$", // upper case "$9", "0X1B", "0H1C", "1DH", "1EX", "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX", }) { ast::hex_literal parsed = {}; auto f = input.begin(), l = input.end(); bool r = parse(f, l, parser::hex_literal, parsed) && f==l; if (r) { std::cout << "result = " << parsed.value << ",\tsource = '" << parsed.source << "'\n"; } else { std::cout << "FAILED" << ",\tremaining: '" << std::string(f,l) << "'\n"; } } }

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex | x3::hex >> hex_qualifier ; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; auto f = input.begin(), l = input.end(); bool r = parse(f, l, parser::hex_literal, parsed) && f==l; std::cout << std::boolalpha << "r = " << r << ",\tresult = " << parsed << ",\tremaining: '" << std::string(f,l) << "'\n"; } }

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit | '0' >> qualifier) >> digits_ | digits_ >> qualifier ; if (x3::parse(f, l, forms, digits)) { attr = std::stoull(digits, nullptr, 16); return true; } return false; } }; hex_literal_type static const hex_literal; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex", // edge cases "ffffffffH", // fits "1ffffffffH", // too big "$00_00___01___________0__________0", // fine "0x", // fine, same as "0h" "$", // upper case "$9", "0X1B", "0H1C", "1DH", "1EX", "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX", }) { ast::hex_literal parsed = 0; auto f = input.begin(), l = input.end(); bool r = parse(f, l, parser::hex_literal, parsed) && f==l; std::cout << std::boolalpha << "r = " << r << ",\tresult = " << parsed << ",\tremaining: '" << std::string(f,l) << "'\n"; } }

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits | hex_digits >> hex_qualifier; } int main() { for (std::string const input : { "$9", "0x1b", "0h1c", "1dh", "1ex", "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex" }) { ast::hex_literal parsed; bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed); std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n"; } }

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

печать

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = false,  result = 9, remaining: '_f'
r = false,  result = 1, remaining: '_fb'
r = false,  result = 1, remaining: '_fc'
r = false,  result = 1, remaining: '1_fdh'
r = false,  result = 1, remaining: '1_fex'
Шаг 3. Сделайте так, чтобы он снова работал с подчеркиванием

Это где я бы начал рассматривать пользовательский парсер. Это потому, что он начнет включать семантическое действие, а также множественные атрибуты, и, честно говоря, удобнее всего их упаковать, чтобы вы могли просто написать императивный C ++ 14, как и любой другой:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper

            auto constexpr max_digits = std::numeric_limits<attribute_type>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr = std::stoull(digits, nullptr, 16);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main() {
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = 0;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r
             << ",\tresult = "    << parsed
             << ",\tremaining: '" << std::string(f,l) << "'\n";
    }
}

Запись как я включилmax_digits чтобы избежать разбора (например, когда на входе есть 10 гигабайт шестнадцатеричных цифр). Возможно, вы захотите улучшить это, предварительно пропустив незначительное0 цифры.

Выход теперь:

r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
r = true,   result = 4294967295,    remaining: ''
r = false,  result = 0, remaining: '1ffffffffH'
r = true,   result = 256,   remaining: ''
r = true,   result = 0, remaining: ''
r = false,  result = 0, remaining: '$'
r = true,   result = 9, remaining: ''
r = true,   result = 27,    remaining: ''
r = true,   result = 28,    remaining: ''
r = true,   result = 29,    remaining: ''
r = true,   result = 30,    remaining: ''
r = true,   result = 159,   remaining: ''
r = true,   result = 507,   remaining: ''
r = true,   result = 508,   remaining: ''
r = true,   result = 509,   remaining: ''
r = true,   result = 510,   remaining: ''
Шаг 4: Обледенение на торте

Если вы хотите сохранить формат ввода для обхода, вы можете просто добавить его в AST сейчас:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    struct hex_literal {
        uintmax_t value;
        std::string source;
    };
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    struct hex_literal_type : x3::parser_base {
        using attribute_type = ast::hex_literal;

        template <typename It, typename Ctx, typename RCtx>
        static bool parse(It& f, It l, Ctx& ctx, RCtx&, attribute_type& attr) {
            std::string digits;

            skip_over(f, l, ctx); // pre-skip using surrounding skipper
            It b = f; // save start

            auto constexpr max_digits = std::numeric_limits<decltype(attr.value)>::digits / 8;
            auto digits_ = x3::skip(x3::as_parser('_')) [x3::repeat(1, max_digits) [ x3::xdigit ] ];

            auto qualifier = x3::omit [ x3::char_("hxHX") ];
            auto forms
                = ('$' >> &x3::digit | '0' >> qualifier) >> digits_
                | digits_ >> qualifier
                ;

            if (x3::parse(f, l, forms, digits)) {
                attr.value = std::stoull(digits, nullptr, 16);
                attr.source.assign(b,l);
                return true;
            }
            return false;
        }
    };

    hex_literal_type static const hex_literal;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex",
            // edge cases
            "ffffffffH", // fits
            "1ffffffffH", // too big
            "$00_00___01___________0__________0", // fine
            "0x", // fine, same as "0h"
            "$",
            // upper case
            "$9",   "0X1B",   "0H1C",   "1DH",   "1EX",
            "$9_F", "0X1_FB", "0H1_FC", "1_FDH", "1_FEX",
    }) {
        ast::hex_literal parsed = {};

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        if (r) {
            std::cout << "result = " << parsed.value
                      << ",\tsource = '" << parsed.source << "'\n";
        }
        else {
            std::cout << "FAILED"
                      << ",\tremaining: '" << std::string(f,l) << "'\n";
        }
    }
}

Печать:

result = 9, source = '$9'
result = 27,    source = '0x1b'
result = 28,    source = '0h1c'
result = 29,    source = '1dh'
result = 30,    source = '1ex'
result = 159,   source = '$9_f'
result = 507,   source = '0x1_fb'
result = 508,   source = '0h1_fc'
result = 509,   source = '1_fdh'
result = 510,   source = '1_fex'
result = 4294967295,    source = 'ffffffffH'
FAILED, remaining: '1ffffffffH'
result = 256,   source = '$00_00___01___________0__________0'
result = 0, source = '0x'
FAILED, remaining: '$'
result = 9, source = '$9'
result = 27,    source = '0X1B'
result = 28,    source = '0H1C'
result = 29,    source = '1DH'
result = 30,    source = '1EX'
result = 159,   source = '$9_F'
result = 507,   source = '0X1_FB'
result = 508,   source = '0H1_FC'
result = 509,   source = '1_FDH'
result = 510,   source = '1_FEX'

¹ Boost Spirit: «Семантические действия - это зло»?

>> &x3::char_("0-9") >> hex_digits

или действительно:

'$' >> &x3::digit >> hex_digits

ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это уменьшает код с огромным запасом

Упростите, шаг 1

Я подозреваю, что вы хотитеинтерпретировать шестнадцатеричные данные в виде чисел, а не строки. Вы могли бы / должны, вероятно, упростить AST соответственно. Шаг 1: отбросьте различие между вещами, проанализированными из 1 или другого формата:

namespace ast {
    using hex_literal = std::string;
}

Теперь вся программа упрощается доЖить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = std::string;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_digits = x3::rule<struct hex_digits_class, ast::hex_literal> {"hex_digits"} 
                          = x3::skip(x3::char_('_')) [ +x3::xdigit ];

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal = 
        ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> hex_digits
        | hex_digits >> hex_qualifier;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        bool r = parse(input.begin(), input.end(), parser::hex_literal, parsed);
        std::cout << "r = " << std::boolalpha << r << ", result = " << parsed << "\n";
    }
}

Печать:

r = true, result = 9
r = true, result = 1b
r = true, result = 1c
r = true, result = 1d
r = true, result = 1e
r = true, result = 9f
r = true, result = 1fb
r = true, result = 1fc
r = true, result = 1fd
r = true, result = 1fe
Шаг 2 (разбор подчеркивания)

Теперь кажется очевидным, что вы действительно хотите знать числовое значение:

Жить на Колиру

#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace ast {
    using hex_literal = uintmax_t;
}

namespace parser {
    namespace x3 = boost::spirit::x3;

    auto const hex_qualifier = x3::omit [ x3::char_("hxHX") ];

    auto const hex_literal 
        = ('$' >> &x3::xdigit | '0' >> hex_qualifier) >> x3::hex
        | x3::hex >> hex_qualifier
        ;
}

int main()
{
    for (std::string const input : { 
            "$9",   "0x1b",   "0h1c",   "1dh",   "1ex",
            "$9_f", "0x1_fb", "0h1_fc", "1_fdh", "1_fex"
    }) {
        ast::hex_literal parsed;

        auto f = input.begin(), l = input.end();
        bool r = parse(f, l, parser::hex_literal, parsed) && f==l;

        std::cout << std::boolalpha
             << "r = "            << r