Boost.Spirit.Qi аварийно завершает работу при назначении правила последовательности, включающей себя

У меня есть следующий MWE:

#include <string>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>

namespace spirit = boost::spirit;
namespace qi = boost::spirit::qi;
namespace phoenix = boost::phoenix;


int main() {

    std::string input("1 2");

    qi::rule<std::string::iterator, void(), qi::space_type> parser;
    qi::rule<std::string::iterator, void(), qi::space_type> parser2;
    qi::rule<std::string::iterator, void(), qi::space_type> parser3;

    parser = qi::int_[
        std::cerr << phoenix::val("First int: ") << qi::_1 << std::endl
    ];

    parser2 = qi::int_[
        std::cerr << phoenix::val("Second int: ") << qi::_1 << std::endl
    ];

    try {
        // Comment out these two lines, (finished below ...)
        parser3 = parser >> parser2;
        phrase_parse(input.begin(), input.end(), parser3, qi::space);

        // ... then un-comment these lines, and the program will crash (and no
        // exception is caught below).
//        parser = parser >> parser2;
//        phrase_parse(input.begin(), input.end(), parser, qi::space);
    }
    catch (...) {
        std::cerr << "Exception caught." << std::endl;
    }

}

Как отмечено в закомментированных строках, если я назначу третье правило qi::rule последовательности из еще двух правил и проанализирую его, используя это третье правило, моя программа будет работать так, как ожидалось. Однако, если я назначу ту же последовательность первому правилу в последовательности, а затем проанализирую это первое правило, программа рухнет, когда я ее запущу, по-видимому, даже не выдав исключения, поскольку блок catch (...) { . . . } не выполняется.

Итак, мой вопрос: есть ли какое-то правило о «qi::rule», которое я должен знать, которое запрещает назначать последовательность, содержащую правило, тому же самому правилу, или это сбой из-за ошибки в Boost.Spirit.Qi?

Намерение

Чтобы пояснить, в свете комментария cv_and_he, моя цель, стоящая за этим маленьким игрушечным примером, состоит в том, чтобы выяснить, как выполнить динамическую генерацию синтаксического анализатора во время выполнения; в частности, как сгенерировать правило из последовательности правил, количество которых известно только во время выполнения, например parser = A1 >> A2 >> ... >> AN;, где N неизвестно во время компиляции, поэтому я не могу просто жестко закодировать одно правило с фиксированным числом ' >>' таким образом. Это было бы похоже на построение списка во время выполнения путем добавления элементов в конец по одному.


person Anthony Hall    schedule 23.11.2013    source источник
comment
stackoverflow.com/a/847455/2417774   -  person llonesmiz    schedule 23.11.2013
comment
@cv_and_he Ваша ссылка на ответ о бесконечной рекурсии дает хорошее представление, спасибо. Так что, по-видимому, я ошибся, думая, что присваивание правила аналогично int i = 0; i = i + 1, поскольку правила в последовательности RHS семантически не похожи на переменные C++, но на самом деле являются нетерминалами в грамматике. Так это я создал грамматику с бесконечной рекурсией? Так является ли сбой, который я вижу, результатом нехватки места в стеке?   -  person Anthony Hall    schedule 23.11.2013
comment
Кажется, я неправильно понял вашу цель. Вы хотите разобрать 1 2 3 4 как (((1 2) 3) 4)? или просто обновить значение parser так же, как в вашем примере с int? Если это последнее, вы можете использовать parser = parser.copy() >> parser2;   -  person llonesmiz    schedule 23.11.2013
comment
Да, это последнее; Моя цель — сформировать новое правило во время выполнения в виде последовательности нетерминалов, которые я сохранил в std::vector; Я использую std::vector отчасти потому, что не знаю количество нетерминалов в последовательности во время выполнения, а только в результате более раннего синтаксического анализа. Таким образом, ранний синтаксический анализ приводит к более позднему созданию правил динамического синтаксического анализатора. Функция parser.copy() — это отличная вещь для изучения; копия правила имеет семантику, которую я не знал, что искал, так что это очень полезно.   -  person Anthony Hall    schedule 23.11.2013
comment
Хотя я этого не проверял, если ваши правила достаточно похожи, почему бы не объединить qi::repeat с qi::lazy?   -  person Mike M    schedule 23.11.2013
comment
@Mike M, правила в моей последовательности не идентичны, поэтому я не понимаю, как qi::repeat поможет - может быть, это было бы, если бы единственная проблема заключалась в повторении одного и того же правила определенное количество раз, известное только во время выполнения. -- Может быть, это то, что вы хотели сказать? Или вы предлагаете использовать qi::repeat для повторения qi::lazy, которые возвращают разные правила при каждом повторе/вызове?   -  person Anthony Hall    schedule 23.11.2013
comment
Нет, я имел в виду повторение подобных правил и использование lazy для установки количества повторений во время выполнения. Они не обязательно должны быть идентичными, но должны по крайней мере доставляться к одному и тому же value_type. Это может быть достигнуто с помощью, например. vector<variant<...>> в качестве целевого типа. Также может прийти на ум трюк Набалика, вы можете добавлять различные правила в анализатор символов во время выполнения...   -  person Mike M    schedule 23.11.2013
comment
использование parser.copy() помогает.   -  person Anthony Hall    schedule 24.11.2013


Ответы (1)


Я не уверен, чего вы пытались достичь, но похоже, что copy() - это то, что вам нужно

    parser = parser.copy() >> parser2;

Смотрите Прямой эфир на Coliru


Задний план

Проблема в том, что Qi берет нетерминалы по ссылке, поэтому вы получаете семантику синтаксического анализатора, которую предлагает грамматика PEG.

Кроме того, деревья выражений Proto (шаблоны выражений) принимают некоторые аргументы по ссылке.

Сочетание этих двух факторов может сильно испортить вашу жизнь, особенно при динамическом парсере построения. Короче говоря, я бы сказал, что снаружи

  • используя унаследованные атрибуты
  • и qi::symbols (включая трюк с Набялеком)

построение правил на лету в Spirit V2 плохо поддерживается. Proto x11 / Spirit X3 могут изменить это к лучшему.

Смотрите больше фона здесь:


Образец кода

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>

namespace spirit = boost::spirit;
namespace qi = boost::spirit::qi;
namespace phoenix = boost::phoenix;


int main() {

    std::string input("1 2");

    qi::rule<std::string::iterator, void(), qi::space_type> parser;
    qi::rule<std::string::iterator, void(), qi::space_type> parser2;
    qi::rule<std::string::iterator, void(), qi::space_type> parser3;

    parser = qi::int_[
        std::cerr << phoenix::val("First int: ") << qi::_1 << std::endl
    ];

    parser2 = qi::int_[
        std::cerr << phoenix::val("Second int: ") << qi::_1 << std::endl
    ];

    try {
        // Comment out these two lines, (finished below ...)
        parser3 = parser >> parser2;
        phrase_parse(input.begin(), input.end(), parser3, qi::space);

        parser = parser.copy() >> parser2;
        phrase_parse(input.begin(), input.end(), parser, qi::space);
    }
    catch (...) {
        std::cerr << "Exception caught." << std::endl;
    }

} 
person sehe    schedule 23.11.2013