Boost Spirit X3 обработчик ошибок с ожиданиями - PullRequest
0 голосов
/ 29 октября 2019

Я хочу создать парсер, использующий 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

1 Ответ

1 голос
/ 08 ноября 2019
  1. Почему существует ошибка ожидания для testParse<true>("x1\nx456\n");?

    (line % x3::eol) будет запущен три раза для этого входа:

    1. Попробуйте line - хорошо (использовать x1), попробуйте x3::eol - хорошо (использовать \n), повторить
    2. Попробуйте line - хорошо (использовать x456), попробуйте x3::eol- хорошо (потреблять \n), повторить
    3. Попробуйте line, он попытается x3::expect[x3::char_("a-zA-Z")], но потерпит неудачу - наступает ошибка ожидания
  2. Обработчик ошибок печатает ошибку, но общий синтаксический анализ не дает сбоя. Почему это происходит?

    При сбое анализатора ожиданий выдается исключение expectation_failure. Однако, когда вы устанавливаете обработчик ошибок для правила - правило перехватит для вас исключение и вызовет ваш обработчик ошибок. Обработчик ошибок сигнализирует о правиле с результатом обработки ошибок, возвращая соответствующее значение типа error_handler_result enum.

    Ваш обработчик ошибок возвращает error_handler_result::fail - это сигнализирует правилу о том, что просто не удалось выполнить синтаксический анализ, эффективно поворачиваяexpect[x] в x. Другими словами, ваш обработчик ошибок - это просто семантическое действие при неудаче (а не при успешном выполнении обычных семантических действий).

    Анализатор списка line % x3::eol - это просто line >> *(x3::eol >> line). Поскольку ваш обработчик ошибок превращает любой ожидаемый сбой в обычный сбой, должно быть очевидно, что после первого успешного разбора line любой сбой не приведет к сбою всего анализа.

  3. И как этоможно исправить?

    Вы не упомянули, что именно хотите. Если вы просто хотите, чтобы распространялось исключение expectation_failure - верните error_handler_result::rethrow из вашего обработчика ошибок.

...