BOOST_SPIRIT_DEFINE не понимаю

Я пытаюсь написать парсер выражений с помощью boost spirit x3. Я основал свой новый код на старом коде, который написал много лет назад (и хорошо работал) с Spirit 2.x (qi).

Ядро моего кода:

   //Make new rule(s) for expression
    auto term = factor >> *(('*' >> factor) | ('/' >> factor));
    auto expression = term >> *(('+' >> term) | ('-' >> term));
    auto group = '(' >> expression >> ')';
    auto factor = lexeme["double_"] | group;

     
    string s="12.4 + 3.2";

    auto first = s.begin();
    auto last = s.end();
    bool r = x3::phrase_parse(
        first,
        last,
        //  Begin grammar
        expression,
        //  End grammar
        x3::space);

Я получаю две ошибки (Visual Studio 2019): Ошибка C2338 BOOST_SPIRIT_DEFINE не определена для этого правила. Ошибка C2039 'parse': не является членом 'boost::spirit::x3::unused_type'

Почему?


person Claudio La Rosa    schedule 04.01.2021    source источник


Ответы (1)


BOOST_SPIRIT_DEFINE используется для связывания статического тега правила с определением (т. е. экземпляром шаблона функции анализа для этого правила).

Хорошая новость заключается в том, что в этом часто нет необходимости, и правила можно определять в режиме реального времени без каких-либо макросов.

Как правило, существуют следующие причины для использования x3::rule:

  1. Когда правила используют рекурсию. Правила, которые вызываются рекурсивно, должны иметь неопределенное правило (неинициализированный объект rule<>) для ссылки (во многом похоже на предварительное объявление).

  2. Чтобы принудить открытый тип атрибута (по моему опыту, потребность в этом немного более распространена в X3, чем в Qi: Понимание оператора списка (%) в Boost.Spirit или, например, boost::spirit::x3 правила совместимости атрибутов, интуиция или код?).

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

  4. #P7# <блочная цитата> #P8#

Ваш образец

Только expression используется рекурсивно. В остальном достаточно просто изменить их порядок:

namespace parser {
    x3::rule<struct expression_> expression{"expression"};
   
    auto group          = '(' >> expression >> ')';
    auto factor         = x3::lexeme["double_"] | group;
    auto term           = factor >> *(('*' >> factor) | ('/' >> factor));
    auto expression_def = term >> *(('+' >> term) | ('-' >> term));

    BOOST_SPIRIT_DEFINE(expression)
}

См. печать в Live On Compiler Explorer:

<expression>
  <try>12.4 + 3.2</try>
  <fail/>
</expression>
------ 12.4 + 3.2
r: false
remaining input: '12.4 + 3.2'

Понятно, что ваше правило factor должно вернуться к норме:

auto factor         = x3::double_ | group;

См. печать в Live On Compiler Explorer:

<expression>
  <try>12.4 + 3.2</try>
  <success></success>
</expression>
------ 12.4 + 3.2
r: true
remaining input: ''

БОНУС: Атрибуты

Добавление распространения атрибута выделит то, что я имел в виду под 2. выше:

namespace Ast {
    struct binop;

    using expression = boost::make_recursive_variant<
        double,
        boost::recursive_wrapper<binop>,
        boost::recursive_variant_
    >::type;

    struct binop {
        char op;
        expression lhs, rhs;
    };
}

Это самое простое, что может сработать. Ваши правила довольно эффективны для построения ast из семантических действий¹:

namespace parser {
    x3::rule<struct expression_, Ast::expression> expression{"expression"};

    auto assign = [](auto& ctx) { _val(ctx) = _attr(ctx); };
    auto make_binop = [](auto& ctx) {
        using boost::fusion::at_c;
        auto& op = at_c<0>(_attr(ctx));
        auto& rhs = at_c<1>(_attr(ctx));
        _val(ctx) = Ast::binop { op, _val(ctx), rhs };
    };
   
    auto group   
        = x3::rule<struct group_, Ast::expression> {"group"}
        = '(' >> expression >> ')';

    auto factor
        = x3::rule<struct factor_, Ast::expression> {"factor"}
        = x3::double_ | group;

    auto term
        = x3::rule<struct term_, Ast::expression> {"term"}
        = factor [assign] >> *(x3::char_("*/") >> factor) [make_binop];

    auto expression_def
        = term [assign] >> *(x3::char_("-+") >> term) [make_binop];

    BOOST_SPIRIT_DEFINE(expression)
}

Посмотрите в Live On Compiler Explorer:

int main() {
    for (std::string const s : {
            "12.4 + 3.2",
        })
    {
        auto f = s.begin(), l = s.end();
        Ast::expression e;
        bool r = x3::phrase_parse(f, l, parser::expression, x3::space, e);

        std::cout
            << "------ " << s << "\n"
            << "r: " << std::boolalpha << r << "\n";

        if (r)
            std::cout << "e: " << e << "\n";

        if (f!=l)
            std::cout << "remaining input: '" << std::string(f,l) << "'\n";
    }
}

Печать

------ 12.4 + 3.2
r: true
e: (12.4 + 3.2)

И вывод отладки:

<expression>
  <try>12.4 + 3.2</try>
  <term>
    <try>12.4 + 3.2</try>
    <factor>
      <try>12.4 + 3.2</try>
      <success> + 3.2</success>
      <attributes>12.4</attributes>
    </factor>
    <success> + 3.2</success>
    <attributes>12.4</attributes>
  </term>
  <term>
    <try> 3.2</try>
    <factor>
      <try> 3.2</try>
      <success></success>
      <attributes>3.2</attributes>
    </factor>
    <success></success>
    <attributes>3.2</attributes>
  </term>
  <success></success>
  <attributes>(12.4 + 3.2)</attributes>
</expression>

¹ Здесь я не упоминаю мою обычную стяжку, потому что использую автоматическое распространение в такого рода грамматике имеет тенденцию приводить к большому количеству возвратов в грамматике, что делает ее неэффективной

person sehe    schedule 05.01.2021
comment
Добавлена ​​демонстрация с распространением атрибутов Live On Compiler Explorer - person sehe; 05.01.2021