Ваш код кажется чрезвычайно сложным для того, чего он добивается. Однако, посмотрев на это в течение значительного времени, я заметил, что вы объявляете правила (которые приводят к их типам атрибутов), но не используете их в решающий момент:
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&
:
'$' >> &x3::char_("0-9") >> hex_digits
или действительно:
'$' >> &x3::digit >> hex_digits
ни одно из правил на самом деле не является рекурсивным, поэтому ни одно из них не требует разделения объявления и определения. Это значительно сокращает код
Упростить, шаг 1
Я подозреваю, что вы хотите интерпретировать шестнадцатеричные данные как числа, а не строку. Возможно, вы могли бы / должны были соответственно упростить AST. Шаг 1: удалите различие между вещами, проанализированными из 1 или другого формата:
namespace ast {
using hex_literal = std::string;
}
Теперь вся программа упрощена до Live On Coliru kbd >
#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 (нарушение синтаксического анализа подчеркивания)
Теперь кажется очевидным, что вы действительно хотите знать числовое значение:
Live On Coliru
#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, как любой другой:
Live On Coliru
#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 сейчас:
Live On Coliru
#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: семантические действия - зло?
person
sehe
schedule
30.10.2017