Boost Spirit X3 AST не работает с семантическими действиями при использовании отдельного определения и создания экземпляра правила

Я пытаюсь использовать Boost Spirit X3 с семантическими действиями при анализе структуры в AST. Если я использую правило без отдельного определения и создания экземпляра, оно работает просто отлично, например:

#include <vector>
#include <string>
#include <iostream>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/home/x3.hpp>

namespace ast 
{

struct ast_struct
{
  int number;
  std::vector<int> numbers;
};

}

BOOST_FUSION_ADAPT_STRUCT(
    ast::ast_struct,
    (int, number)
    (std::vector<int>, numbers)
)

namespace x3 = boost::spirit::x3;
using namespace std;

void parse( const std::string &data )
{
  string::const_iterator begin = data.begin();
  string::const_iterator end = data.end();

  unsigned n(0);

  auto f = [&n]( auto &ctx )
    {
      n = x3::_attr(ctx);
    };

  ast::ast_struct ast;
  bool r = x3::parse( begin, end, 
                      x3::int_[f] >> +( x3::omit[+x3::blank] >> x3::int_ ), ast );

  if ( r && begin == end )
  {
    cout << "n: " << n << ", ";
    std::copy(ast.numbers.begin(), ast.numbers.end(), 
              std::ostream_iterator<int>(std::cout << ast.numbers.size() << " elements: ", " "));
    cout << endl;
  }
  else
    cout << "Parse failed" << endl;
}

int main()
{
  parse( "3 1 2 3" );
  parse( "4 1 2 3 4" );
  return 0;
}

Выполнение приведенного выше кода (скомпилированного с флагами -std= C++14) дает ожидаемый результат:

n: 3, 3 elements: 1 2 3 
n: 4, 4 elements: 1 2 3 4 

Теперь я пытаюсь, чтобы мой парсер Spirit X3 был организован более или менее так же, как пример calc 9 из Boost Spirit X3, но он не работает:

  • ast.hxx: определяет абстрактное синтаксическое дерево.
  • grammar.hxx: пользовательский интерфейс, предоставляющий методы парсера.
  • grammar.cxx: создает экземпляры правил.
  • grammar_def.hxx: определение грамматики синтаксического анализатора.
  • config.hxx: конфигурация парсера.
  • main.cxx: пример использования парсера.

ast.hxx:

#ifndef AST_HXX
#define AST_HXX

#include <vector>
#include <boost/fusion/include/adapt_struct.hpp>

namespace ast 
{

struct ast_struct
{
  int number;
  std::vector<int> numbers;
};

}

BOOST_FUSION_ADAPT_STRUCT(
    ast::ast_struct,
    (int, number)
    (std::vector<int>, numbers)
)

#endif

grammar.hxx:

#ifndef GRAMMAR_HXX
#define GRAMMAR_HXX

#include "ast.hxx"
#include <boost/spirit/home/x3.hpp>

namespace parser 
{

namespace x3 = boost::spirit::x3;

using my_rule_type = x3::rule<class my_rule_class, ast::ast_struct>;

BOOST_SPIRIT_DECLARE( my_rule_type );

const my_rule_type &get_my_rule();

}

#endif

grammar.cxx:

#include "grammar_def.hxx"
#include "config.hxx"

namespace parser 
{

BOOST_SPIRIT_INSTANTIATE( my_rule_type, iterator_type, context_type )

}

grammar_def.hxx:

#ifndef GRAMMAR_DEF_HXX
#define GRAMMAR_DEF_HXX

#include <iostream>
#include <boost/spirit/home/x3.hpp>
#include "grammar.hxx"
#include "ast.hxx"

namespace parser 
{
namespace x3 = boost::spirit::x3;

const my_rule_type  my_rule( "my_rule" );

unsigned n;

auto f = []( auto &ctx )
{
  n = x3::_attr(ctx);
};

auto my_rule_def =  x3::int_[f] >> +( x3::omit[+x3::blank] >> x3::int_ );

BOOST_SPIRIT_DEFINE( my_rule )

const my_rule_type &get_my_rule()
{
  return my_rule;
}

}

#endif

config.hxx:

#ifndef CONFIG_HXX
#define CONFIG_HXX

#include <string>
#include <boost/spirit/home/x3.hpp>

namespace parser 
{

namespace x3 = boost::spirit::x3;

using iterator_type = std::string::const_iterator;
using context_type = x3::unused_type;

}

#endif

main.cxx:

#include "ast.hxx"
#include "grammar.hxx"
#include "config.hxx"
#include <iostream>
#include <boost/spirit/home/x3.hpp>
#include <string>

namespace x3 = boost::spirit::x3;
using namespace std;

void parse( const std::string &data )
{
  parser::iterator_type begin = data.begin();
  parser::iterator_type end = data.end();

  ast::ast_struct ast;
  cout << "Parsing [" << string(begin,end) << "]" << endl;

  bool r = x3::parse( begin, end, parser::get_my_rule(), ast );

  if ( r && begin == end )
  {
    std::copy(ast.numbers.begin(), ast.numbers.end(), 
              std::ostream_iterator<int>(std::cout << ast.numbers.size() << " elements: ", " "));
    cout << endl;
  }
  else
    cout << "Parse failed" << endl;
}

int main()
{
  parse( "3 1 2 3" );
  parse( "4 1 2 3 4" );
  return 0;
}

Компиляция main.cxx и grammar.cxx (флаги: -std= C++14) и выполнение кода выше выдает:

Parsing [3 1 2 3]
0 elements: 
Parsing [4 1 2 3 4]
0 elements: 

Я прошу прощения за длинный исходный код, я постарался сделать его как можно меньше.

Обратите внимание, что у меня есть некоторое использование для глобальной переменной без знака n, она будет использоваться с пользовательской директивой повтора (см. Вопрос здесь и одно из решений здесь). Чтобы сфокусировать вопрос, я удалил повторяющуюся часть из этого вопроса, поэтому, хотя я мог удалить семантическое действие в этом примере, это не было возможным решением.

Я был бы признателен за некоторую помощь, чтобы раскрыть эту проблему, мне не понятно, почему приведенный выше код не работает. Заранее спасибо.

1 ответ

Решение

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

Тем не менее, я знаю ответ и хитрость, чтобы сделать вашу жизнь проще.

Ответ

Семантические действия в определении правила запрещают автоматическое распространение атрибутов. Из документов Qi (то же самое касается X3, но я всегда теряю ссылку на документы):

r = p; Определение правила
Это эквивалентно r %= p (см. Ниже), если в p нет семантических действий.

r% = p; Автоматическое определение
Атрибут p должен быть совместим с синтезированным атрибутом r. Когда p успешен, его атрибут автоматически распространяется на синтезированный атрибут r.

Трюк

Вы можете ввести состояние (ваш n в данном случае) с использованием x3::with<> директивы. Таким образом, у вас нет глобального пространства имен (n) и может сделать парсер реентерабельным, поточно-безопасным и т. д.

Вот мой "упрощенный" взгляд на вещи в одном файле:

namespace parsing {
    x3::rule<struct parser, ast::ast_struct> parser {"parser"};

    struct state_tag { };

    auto record_number = [](auto &ctx) { 
        unsigned& n = x3::get<state_tag>(ctx);
        n = x3::_attr(ctx); 
    };

    auto parser_def = x3::rule<struct parser_def, ast::ast_struct> {} 
                   %= x3::int_[record_number] >> +(x3::omit[+x3::blank] >> x3::int_);

    BOOST_SPIRIT_DEFINE(parser)
}

Совет: запустите демо с = вместо %= чтобы увидеть разницу в поведении!

Обратите внимание, что get<state_tag>(ctx) возвращает reference_wrapper<unsigned> только потому, что мы используем парсер следующим образом:

void parse(const std::string &data) {
    using namespace std;

    ast::ast_struct ast;
    unsigned n;
    auto parser = x3::with<parsing::state_tag>(ref(n)) [parsing::parser] >> x3::eoi;

    if (x3::parse(data.begin(), data.end(), parser, ast)) {
        cout << "n: " << n << ", ";
        copy(ast.numbers.begin(), ast.numbers.end(), ostream_iterator<int>(cout << ast.numbers.size() << " elements: ", " "));
        cout << "\n";
    } else
        cout << "Parse failed\n";
}

Live Demo

Жить на Колиру

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iostream>

namespace ast {
    struct ast_struct {
        int number;
        std::vector<int> numbers;
    };
}

BOOST_FUSION_ADAPT_STRUCT(ast::ast_struct, number, numbers)

namespace x3 = boost::spirit::x3;

namespace parsing {
    x3::rule<struct parser, ast::ast_struct> parser {"parser"};

    struct state_tag { };

    auto record_number = [](auto &ctx) { 
        unsigned& n = x3::get<state_tag>(ctx); // note: returns reference_wrapper<T>
        n = x3::_attr(ctx); 
    };

    auto parser_def = x3::rule<struct parser_def, ast::ast_struct> {} 
                   %= x3::int_[record_number] >> +(x3::omit[+x3::blank] >> x3::int_);

    BOOST_SPIRIT_DEFINE(parser)
}

void parse(const std::string &data) {
    using namespace std;

    ast::ast_struct ast;
    unsigned n = 0;
    auto parser = x3::with<parsing::state_tag>(ref(n)) [parsing::parser] >> x3::eoi;

    if (x3::parse(data.begin(), data.end(), parser, ast)) {
        cout << "n: " << n << ", ";
        copy(ast.numbers.begin(), ast.numbers.end(), ostream_iterator<int>(cout << ast.numbers.size() << " elements: ", " "));
        cout << "\n";
    } else
        cout << "Parse failed\n";
}

int main() {
    parse("3 1 2 3");
    parse("4 1 2 3 4");
}

Печать

n: 3, 3 elements: 1 2 3 
n: 4, 4 elements: 1 2 3 4 
Другие вопросы по тегам