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