【发布时间】: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