在使用单独的规则定义和实例化时,Boost Spirit X3 AST 无法处理语义操作

Boost Spirit X3 AST not working with semantic actions when using separate rule definition and instantiation

我正在尝试将 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 解析器的组织方式与 Boost Spirit X3 的 calc 9 example 大致相同,但它不起作用:

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: 

对于较长的源代码,我深表歉意,我尽量让它尽可能小。

请注意我对 unsigned n 全局变量有一些用法,它将与自定义重复指令一起使用(参见 and one of the solutions here)。为了保持问题的重点,我从这个问题中删除了重复部分,所以即使我可以删除这个例子中的语义动作,这也不是一个可能的解决方案。

如果能帮我解决这个问题,我将不胜感激,我不清楚为什么上面的代码不起作用。提前谢谢你。

我必须承认实际上重建你的样本对我来说有点太多工作(叫我懒惰...)。

不过,我知道答案和让你的生活更简单的诀窍。

答案

对规则定义的语义操作禁止自动属性传播。来自 Qi docs(X3 也是如此,但我总是把 link 丢给文档):

r = p; Rule definition
This is equivalent to r %= p (see below) if there are no semantic actions attached anywhere in p.

r %= p; Auto-rule definition
The attribute of p should be compatible with the synthesized attribute of r. When p is successful, its attribute is automatically propagated to r's synthesized attribute.

绝招

您可以使用 x3::with<> 指令注入状态(在本例中为您的 n 引用)。这样你就没有全局命名空间(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)
}

Tip: run the demo with = instead of the %= to see the difference in behaviour!

注意get<state_tag>(ctx) returns一个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 On Coliru

#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