Я хочу создать парсер, использующий boost::spirit::x3
для файлов на основе строки, например, каждая строка имеет одинаковую структуру и может быть повторена. Далее я хочу подробное описание ошибки в случае ошибки. Наконец, возможно, что файл заканчивается символом новой строки.
Теперь я столкнулся с некоторым странным поведением в случае использования x3::expect
в первом элементе строки. Обработчик ошибок печатает ошибку, но общий анализ не завершается ошибкой. Почему это происходит? И как это можно исправить? Если я не ожидаю первого элемента строки, я не получу подробное описание ошибки.
Вот пример, воспроизводящий эту проблему:
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/utility/annotate_on_success.hpp>
#include <boost/spirit/home/x3/support/utility/error_reporting.hpp>
#include <boost/fusion/include/define_struct.hpp>
#include <iostream>
namespace x3 = boost::spirit::x3;
struct error_handler
{
template<typename Iterator, typename Exception, typename Context>
x3::error_handler_result on_error(Iterator& first, Iterator const& last,
Exception const& x,
Context const& context)
{
auto& error_handler = x3::get<x3::error_handler_tag>(context).get();
std::string message = "Error! Expecting: " + x.which() + " here:";
error_handler(x.where(), message);
return x3::error_handler_result::fail;
}
};
namespace boost::spirit::x3 {
template<>
struct get_info<int_type>
{
std::string operator()(int_type const&) const
{ return "integral number"; }
};
template<>
struct get_info<char_type>
{
std::string operator()(char_type const&) const
{ return "character"; }
};
} // namespace boost::spirit::x3
struct Line_tag : error_handler
{
};
struct File_tag : error_handler
{
};
BOOST_FUSION_DEFINE_STRUCT((), Data, (char, c)(int, x))
BOOST_FUSION_DEFINE_STRUCT((), DataContainer, (std::vector<Data>, data))
template<bool ExpectFirstElementOfLine>
DataContainer parse(std::string_view input)
{
auto iter = input.cbegin();
auto const end = input.cend();
const auto charParser = []() {
if constexpr (ExpectFirstElementOfLine)
return x3::expect[x3::char_("a-zA-Z")];
else
return x3::char_("a-zA-Z");
}();
const auto line = x3::rule<Line_tag, Data>{"line"} = charParser > x3::int_;
const auto file = x3::rule<File_tag, DataContainer>{"file"} = (line % x3::eol) >> -x3::eol >> x3::eoi;
x3::error_handler<decltype(iter)> error_handler(iter, end, std::cout);
DataContainer container;
if (parse(iter, end, x3::with<x3::error_handler_tag>(std::ref(error_handler))[file], container))
{
if (iter != end)
throw std::runtime_error("Remaining unparsed");
}
else
throw std::runtime_error("Parse failed");
return container;
}
template<bool ExpectFirstElementOfLine>
void testParse(std::string_view input)
{
try
{
std::cout << "=========================" << std::endl;
const auto container = parse<ExpectFirstElementOfLine>(input);
std::cout << "Parsed [OK]: " << container.data.size() << std::endl;
}
catch (const std::exception& ex)
{
std::cout << "EXCEPTION: " << ex.what() << std::endl;
}
}
int main()
{
const std::string_view input1 = "x1\nx456";
const std::string_view input2 = "x1\nx456\n";
const std::string_view input3 = "x1\n456\n";
// OK
testParse<true>(input1);
testParse<false>(input1);
// parse succeeds but error handler prints message if expectation on first element of line is used
testParse<true>(input2);
testParse<false>(input2);
// parsing fails but detailed error description only works if first element of line was expected
testParse<true>(input3);
testParse<false>(input3);
}
, что приводит к:
=========================
Parsed [OK]: 2
=========================
Parsed [OK]: 2
=========================
In line 3:
Error! Expecting: char-set here:
^_
Parsed [OK]: 2
=========================
Parsed [OK]: 2
=========================
In line 2:
Error! Expecting: char-set here:
456
^_
EXCEPTION: Parse failed
=========================
EXCEPTION: Parse failed