【问题标题】:Spirit X3, How to get attribute type to match rule type?Spirit X3,如何获取属性类型以匹配规则类型?
【发布时间】:2019-06-29 16:30:12
【问题描述】:

为了开发 Spirit X3 解析器,我想使用语义操作(脚注 1)。控制如何将属性存储到 STL 容器中对我来说很重要。

这个问题是关于如何控制解析器属性:_attr(ctx)匹配规则类型:_val(ctx),以便正确分配。也许这个问题归结为如何应用无证 transform_attribute 功能。但是请和我一起阅读,看看这是否真的是在示例代码中为我解决的问题。

对象/变量的打印类型

当我尝试不同的语法表达式时,我发现非常有用的是能够在语义操作中打印 _attr( ctx ) 和 _val( ctx ) 的类型。

所以根据Howard Hinnant的回答,我写了一个实用程序头文件,根据自己的喜好提供这样的设施。

code below is to be put in a file named utility.h

#include <string>
#include <type_traits>
#include <typeinfo>
#include <cxxabi.h>

namespace utility
{

template<typename T>
std::string type2string()
{
  std::string r;
  typedef typename std::remove_reference<T>::type TR;

  std::string space = "";
  if ( std::is_const<TR>::value )
    { r = "const"; space = " "; }
  if ( std::is_volatile<TR>::value )
    { r += space + " volatile"; space = " "; }

  int status;
  char* demangled =
    abi::__cxa_demangle( typeid(TR).name(), nullptr, nullptr, &status );
  switch ( status )
  {
    case  0: { goto proceed; }
    case -1: { r = "type2string failed: malloc failure"; goto fail; }
    case -2: { r = "type2string failed: " + std::string(typeid(TR).name()) +
      " nonvalid C++ ABI name"; goto fail; }
    case -3: { r = "type2string failed: invalid argument(s)"; goto fail; }
    default: { r = "type2string failed: unknown status " +
      status; goto fail; }
  }
  proceed:
  r += space + demangled;
  free( demangled );

  /* references are without a space */
  if ( std::is_lvalue_reference<T>::value ) { r += '&'; }
  if ( std::is_rvalue_reference<T>::value ) { r += "&&"; }

  fail:
  return r;
}

}

现在实际工作的示例代码:

#include <cstddef>
#include <cstdio>
#include <cstdint>

#define BOOST_SPIRIT_X3_DEBUG
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/home/x3.hpp>

#include <string>
#include <vector>
#include <utility> // this is for std::move
#include "utility.h" // to print types

namespace client
{
  namespace x3 = boost::spirit::x3;
  namespace ascii = boost::spirit::x3::ascii;

  namespace semantic_actions
  {
    using x3::_val;  // assign to _val( ctx )
    using x3::_attr; // from _attr( ctx )    

    struct move_assign
    {  
      template <typename Context>
      void operator()(const Context& ctx) const
      {
        printf( "move_assign\n" );
        _val( ctx ) = std::move( _attr( ctx ) );
      }
    };

    struct print_type
    {
      template <typename Context>
      void operator()(const Context& ctx) const
      {
        printf( "print_type\n" );

        std::string str;
        str = utility::type2string< decltype( _attr( ctx ) ) >();
        printf( "_attr type: %s\n", str.c_str() );

        // reuse str
        str = utility::type2string< decltype( _val( ctx ) ) >();
        printf( "_val type: %s\n", str.c_str() );
      }
    };
  }

  namespace parser
  {
    using x3::char_;
    using x3::lit;
    using namespace semantic_actions;

    x3::rule<struct main_rule_class, std::string> main_rule_ = "main_rule";

    const auto main_rule__def = (*( !lit(';') >> char_) >> lit(';'))[print_type()][move_assign()];

    BOOST_SPIRIT_DEFINE( main_rule_ )

    const auto entry_point = x3::skip(x3::space)[ main_rule_ ];
  }
}

int main()
{
  printf( "Give me a string to test rule.\n" );
  printf( "Type [q or Q] to quit.\n" );

  std::string input_str;
  std::string output_str;

  while (getline(std::cin, input_str))
  {
    if ( input_str.empty() || input_str[0] == 'q' || input_str[0] == 'Q')
    { break; }

    auto first = input_str.begin(), last = input_str.end();

    if ( parse( first, last, client::parser::entry_point, output_str) )
    {
      printf( "Parsing succeeded\n" );
      printf( "input:  \"%s\"\n", input_str.c_str() );
      printf( "output: \"%s\"\n", output_str.c_str() );
    }
    else
    {
      printf( "Parsing failed\n" );
    }
  }

  return 0;
}

输入总是: abcd;

输出:

Give me a string to test rule.
Type [q or Q] to quit.
<main_rule>
  <try>abcd;</try>
print_type
_attr type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&
_val type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&
move_assign
  <success></success>
  <attributes>[a, b, c, d]</attributes>
</main_rule>
Parsing succeeded
input:  "abcd;"
output: "abcd"

好的,到目前为止一切都很好,但假设我想在解析结果中包含分号。我将语法行更改为:

const auto main_rule__def = (*( !lit(';') >> char_) >> char_(";"))[print_type()];

注意:我删除了语义操作 [move_assign()],因为它由于 _attr 和 _val 类型不兼容而无法编译。 现在的输出是:

Give me a string to test rule.
Type [q or Q] to quit.
<main_rule>
  <try>abcd;</try>
print_type
_attr type: boost::fusion::deque<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>&
_val type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&
  <success></success>
  <attributes>[]</attributes>
</main_rule>
Parsing succeeded
input:  "abcd;"
output: ""

现在 boost::fusion::deque 的 _attr 类型不是我想要的,我只是想成为 std::string。我不明白为什么如果我在语义动作括号内有语法分配的完整右侧,_attr 仍然不是 _val 类型。 X3 功能 transform_attribute 在这里有帮助吗?我应该如何应用它?或者有什么好的方法可以解决这个问题,而不必使用 boost fusion 类接口或其他实现细节。

当前解决方法

我目前的解决方法是定义另一个规则,只是从第一个规则中分配一个语义操作。只有 _attr 是 std::string 类型。

  namespace parser
  {
    using x3::char_;
    using x3::lit;
    using namespace semantic_actions;

    x3::rule<struct main_rule_class, std::string> main_rule_ = "main_rule";
    x3::rule<struct main_rule2_class, std::string> main_rule2_ = "main_rule2";

    const auto main_rule__def = *( !lit(';') >> char_) >> char_(";");
    const auto main_rule2__def = main_rule_[print_type()][move_assign()];

    BOOST_SPIRIT_DEFINE( main_rule_, main_rule2_ )

    const auto entry_point = x3::skip(x3::space)[ main_rule2_ ];
  }

输出:

Give me a string to test rule.
Type [q or Q] to quit.
<main_rule2>
  <try>abcd;</try>
  <main_rule>
    <try>abcd;</try>
    <success></success>
    <attributes>[a, b, c, d, ;]</attributes>
  </main_rule>
print_type
_attr type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&
_val type: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&
move_assign
  <success></success>
  <attributes>[a, b, c, d, ;]</attributes>
</main_rule2>
Parsing succeeded
input:  "abcd;"
output: "abcd;"

我希望有一种方法,而不必制定另一个规则来让 _attr 的类型与 _val 匹配。

(1) 我不欣赏作者在这个库中隐藏的聪明才智。因为只是一个看似无辜的变化就可能破坏应用程序。而更明确和更精细的方法将更清楚地传达正在发生的事情。我只需要把它从我的胸膛中解脱出来。

【问题讨论】:

  • 哦,是的,它是 Spirit v3.0.3,包含在 boost 1.69.0 中。使用 mingw-w64 GCC 8.1 在 C++14 模式下编译。
  • 关于“隐藏的聪明”——这其实是卖点。就像任何其他具有约定/启发式的框架一样:当您知道如何导航边缘时,这是一个最佳点。否则,我会考虑手动滚动解析器或使用适当的代码生成器,如 ANTLR、CoCo/C++ 等。

标签: c++ boost boost-spirit boost-spirit-x3


【解决方案1】:

直接回答

transform_attribute 尚未为 X3 (https://www.boost.org/doc/libs/1_70_0/libs/spirit/doc/x3/html/index.html) 记录,但您可以在此处找到其 Qi 对应项:https://www.boost.org/doc/libs/1_70_0/libs/spirit/doc/html/spirit/advanced/customize/transform.html。

X3 功能 transform_attribute 在这里有帮助吗?我应该如何应用它?

无论如何,这是一个您可以通过使用规则轻松访问的实现细节。我喜欢使用匿名规则来帮助解决这个问题:

template <typename T>
    struct as_type {
        template <typename E>
        constexpr auto operator[](E e) const { return x3::rule<struct _, T> {} = e; }
    };

template <typename T>
    static inline constexpr as_type<T> as;

现在你可以写了

const auto main_rule__def = as<std::string> [ (*(char_ - ';') >> char_(';')) ];

Live On Coliru

#include <iostream>
//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <iomanip> // std::quoted

namespace client {
    namespace x3 = boost::spirit::x3;
    namespace ascii = boost::spirit::x3::ascii;

    namespace parser {
        using x3::char_;
        using x3::lit;

        x3::rule<struct main_rule_class, std::string> main_rule_ = "main_rule";

        template <typename T>
            struct as_type {
                template <typename E>
                constexpr auto operator[](E e) const { return x3::rule<struct _, T> {} = e; }
            };

        template <typename T>
            static inline constexpr as_type<T> as;

        const auto main_rule__def = as<std::string> [ (*(char_ - ';') >> char_(';')) ];

        BOOST_SPIRIT_DEFINE(main_rule_)

        const auto entry_point = x3::skip(x3::space)[main_rule_];
    } // namespace parser
} // namespace client

int main() {
    std::string output_str;
    for(std::string const input_str : { "abcd;" }) {
        auto first = input_str.begin(), last = input_str.end();

        if (parse(first, last, client::parser::entry_point, output_str)) {
            std::cout << "Parsing succeeded\n";
            std::cout << "input:  " << std::quoted(input_str) << "\n";
            std::cout << "output:  " << std::quoted(output_str) << "\n";
        } else {
            std::cout << "Parsing failed\n";
        }
    }
}

打印

Parsing succeeded
input:  "abcd;"
output:  "abcd;"

理论上可能会有性能开销,但我强烈怀疑所有编译器都会在这里内联所有内容,因为没有任何东西有外部链接或 vtables,而且所有内容都是 const/constexpr。

替代、简化:

使用x3::raw

在这种情况下,您可以使用现有指令获得所需的行为:x3::raw

Live On Coliru

const auto main_rule__def = x3::raw [ *(char_ - ';') >> ';' ];

不要总是使用rule&lt;&gt;

仅当您有递归规则或需要对规则进行外部链接时才需要(在单独的翻译单元中定义它们)。整个程序缩小到...

Live On Coliru

#include <iostream>
#include <boost/spirit/home/x3.hpp>
#include <iomanip> // std::quoted

namespace x3 = boost::spirit::x3;
namespace client::parser {
    auto const entry_point = x3::raw [ *(x3::char_ - ';') >> ';' ];
}

int main() {
    for(std::string const input : { "abcd;" }) {
        std::string output;
        if (parse(input.begin(), input.end(), client::parser::entry_point, output)) {
            std::cout << "Parsing succeeded\n";
            std::cout << "input:  " << std::quoted(input) << "\n";
            std::cout << "output: " << std::quoted(output) << "\n";
        } else {
            std::cout << "Parsing failed\n";
        }
    }
}

最后——关于跳过

我不认为你想要char_ - ';'(或者更复杂的拼写方式:!lit(';') &gt;&gt; char_)。使用船长,它将跨空格解析 ("ab c\nd ;" -> "abcd;"`)。

您可能希望使规则更具限制性(例如 lexeme [+(graph - ';')] 或什至只是 raw[lexeme[+(alnum|'_')] 或 lexeme[+char_("a-zA-Z0-9_")])。

见Boost spirit skipper issues

【讨论】:

  • 再次感谢 sehe 的出色回答!如您所知,我的风格比您的风格更冗长,但尤其是在语法表达式中,消除样板文件很重要,例如我必须定义的额外规则。您的 as 工具非常适合用于此目的。我测试了它,它也对我有用。
  • 对于调试匿名规则名称太长了。所以我指定了一个这样的空字符串: return x3::rule {""} = e;
  • 关于使用 x3::raw,你强迫精神只使用迭代器而不是 boost fusion,对吧?我记得作者曾经在精神邮件列表上提到过没有使用 raw 进行构造。但显然没有语义操作,这个解析器仍然能够将结果放在 std::string 中。这是否意味着精神在某处调用 basic_string( InputIt first, InputIt last ) 构造函数?这对其他类型有何作用?也许我可以将 x3::raw 放在特定的语法片段上。但有时我只想获取语义动作中的实际类型。
  • 它确实返回了一个迭代器范围。但是,迭代器范围现在是只是合成属性类型,并且传播仍然符合所有属性传播规则,包括Boost Fusion(您实际上可以看出,因为否则它不会传播到@ 987654345@).
  • “但有时我只想获取实际类型” - std::string 是“实际类型”(实际上,在 AST 中)。如果你的意思是“实际上是合成类型”,我敢说这很少是真的,但你可以:coliru.stacked-crooked.com/a/2cd2ddc904d7a6c7 甚至更糟:coliru.stacked-crooked.com/a/4d714f2156304206
【解决方案2】:

使用 char_(';'),属性有 2 个部分。这两个部分都需要添加到_val。比如:

namespace semantic_actions
{
  using x3::_val;  // assign to _val( ctx )
  using x3::_attr; // from _attr( ctx )    
  using boost::fusion::at_c;

  struct move_assign
  {  
    template <typename Context>
    void operator()(const Context& ctx) const
    {
      printf( "move_assign\n" );
      auto attr=_attr( ctx );
      _val( ctx ) = at_c<0>( attr );
      _val( ctx ) += at_c<1>( attr );       
    }
  };
.
.
.
}

【讨论】:

  • 如果需要像这样笨拙的代码,我不会使用解析器生成器。不过,它确实有效,所以支持它。
  • 感谢您的建议帮助我。我知道可以使用融合类接口,但这是我想避免的。我想我会听从 sehe 的建议,这不会让你感到惊讶。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-08
  • 1970-01-01
  • 1970-01-01
  • 2021-03-18
  • 1970-01-01
  • 2018-03-19
  • 2019-09-28
相关资源
最近更新 更多