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 более или менее так же, как пример расчета 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:

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

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

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

5

Решение

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

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

Ответ

Семантические действия в определении правила запрещают автоматическое распространение атрибутов. От Ци документы (то же самое касается 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
6

Другие решения

Других решений пока нет …