我正在玩Boost.Spirit。作为一项大型工作的一部分,我正在尝试构造一个用于解析C / C ++样式字符串文字的语法。我遇到了一个问题:
如何创建将std::string()
结果附加到调用语法的std::string()
属性 的子语法(相反)只是一个char
?
这是我的代码,到目前为止一直在运行。 (实际上我已经获得了更多,包括'\n'
之类的东西,但我把它归结为必需品。)
#define BOOST_SPIRIT_UNICODE
#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
using namespace boost;
using namespace boost::spirit;
using namespace boost::spirit::qi;
template < typename Iterator >
struct EscapedUnicode : grammar< Iterator, char() > // <-- should be std::string
{
EscapedUnicode() : EscapedUnicode::base_type( escaped_unicode )
{
escaped_unicode %= "\\" > ( ( "u" >> uint_parser< char, 16, 4, 4 >() )
| ( "U" >> uint_parser< char, 16, 8, 8 >() ) );
}
rule< Iterator, char() > escaped_unicode; // <-- should be std::string
};
template < typename Iterator >
struct QuotedString : grammar< Iterator, std::string() >
{
QuotedString() : QuotedString::base_type( quoted_string )
{
quoted_string %= '"' >> *( escaped_unicode | ( char_ - ( '"' | eol ) ) ) >> '"';
}
EscapedUnicode< Iterator > escaped_unicode;
rule< Iterator, std::string() > quoted_string;
};
int main()
{
std::string input = "\"foo\u0041\"";
typedef std::string::const_iterator iterator_type;
QuotedString< iterator_type > qs;
std::string result;
bool r = parse( input.cbegin(), input.cend(), qs, result );
std::cout << result << std::endl;
}
这会打印fooA
- QuotedString
语法调用EscapedUnicode
语法,这会导致char
被添加到std::string
属性QuotedString
1}}(A
,0x41
)。
但是当然我需要为超过0x7f的任何内容生成字符(字节)的序列。 EscapedUnicode
需要生成一个std::string
,必须追加到QuotedString
生成的字符串。
这就是我遇到障碍的地方。我不理解Boost.Spirit与Boost.Phoenix一起做的事情,我所做的任何尝试都会导致冗长且几乎无法解释的与模板相关的编译器错误。
那么,我该怎么做呢?答案实际上不需要进行正确的Unicode转换;它是我需要解决方案的std::string
问题。
答案 0 :(得分:5)
应用了几点:
using namespace
与高度通用的代码相关。除非你控制它,否则ADL会破坏你的一天%=
是自动规则分配,这意味着即使存在语义操作,也会强制执行自动属性传播。您不希望这样,因为如果您要编码为多字节字符串表示形式,uint_parser
公开的属性不会(正确)自动传播。输入字符串
std::string input = "\"foo\u0041\"";
需要
std::string input = "\"foo\\u0041\"";
否则编译器在解析器运行之前执行了转义处理:)
这里有针对任务的特定技巧:
您需要将规则的声明属性更改为Spirit将自动“变平”的内容。以简单的顺序。 E.g。
quoted_string = '"' >> *(escaped_unicode | (qi::char_ - ('"' | qi::eol))) >> '"';
不会追加,因为alternate的第一个分支导致char序列,而第二个分支导致单个char。以下是等效的拼写:
quoted_string = '"' >> *(escaped_unicode | +(qi::char_ - ('"' | qi::eol | "\\u" | "\\U"))) >> '"';
在Spirit中巧妙地触发附加启发式,因此我们可以实现我们想要的without involving Semantic Actions。
其余的是直截了当的:
使用Phoenix函数对象实现实际编码:
struct encode_f {
template <typename...> struct result { using type = void; };
template <typename V, typename CP> void operator()(V& a, CP codepoint) const {
// TODO implement desired encoding (e.g. UTF8)
bio::stream<bio::back_insert_device<V> > os(a);
os << "[" << std::hex << std::showbase << std::setw(std::numeric_limits<CP>::digits/4) << std::setfill('0') << codepoint << "]";
}
};
boost::phoenix::function<encode_f> encode;
然后您可以使用:
escaped_unicode = '\\' > ( ("u" >> uint_parser<uint16_t, 16, 4, 4>() [ encode(_val, _1) ])
| ("U" >> uint_parser<uint32_t, 16, 8, 8>() [ encode(_val, _1) ]) );
因为您提到您并不关心特定的编码,所以我选择以16位或32位十六进制表示形式编码原始代码点,如
[0x0041]
。我务实地使用了Boost Iostreams,它能够直接写入属性的容器类型
使用BOOST_SPIRIT_DEBUG*
宏
<强> Live On Coliru 强>
//#define BOOST_SPIRIT_UNICODE
//#define BOOST_SPIRIT_DEBUG
#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
// for demo re-encoding
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/stream.hpp>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace bio = boost::iostreams;
namespace phx = boost::phoenix;
template <typename Iterator, typename Attr = std::vector<char> > // or std::string for that matter
struct EscapedUnicode : qi::grammar<Iterator, Attr()>
{
EscapedUnicode() : EscapedUnicode::base_type(escaped_unicode)
{
using namespace qi;
escaped_unicode = '\\' > ( ("u" >> uint_parser<uint16_t, 16, 4, 4>() [ encode(_val, _1) ])
| ("U" >> uint_parser<uint32_t, 16, 8, 8>() [ encode(_val, _1) ]) );
BOOST_SPIRIT_DEBUG_NODES((escaped_unicode))
}
struct encode_f {
template <typename...> struct result { using type = void; };
template <typename V, typename CP> void operator()(V& a, CP codepoint) const {
// TODO implement desired encoding (e.g. UTF8)
bio::stream<bio::back_insert_device<V> > os(a);
os << "[0x" << std::hex << std::setw(std::numeric_limits<CP>::digits/4) << std::setfill('0') << codepoint << "]";
}
};
boost::phoenix::function<encode_f> encode;
qi::rule<Iterator, Attr()> escaped_unicode;
};
template <typename Iterator>
struct QuotedString : qi::grammar<Iterator, std::string()>
{
QuotedString() : QuotedString::base_type(start)
{
start = quoted_string;
quoted_string = '"' >> *(escaped_unicode | +(qi::char_ - ('"' | qi::eol | "\\u" | "\\U"))) >> '"';
BOOST_SPIRIT_DEBUG_NODES((start)(quoted_string))
}
EscapedUnicode<Iterator> escaped_unicode;
qi::rule<Iterator, std::string()> start;
qi::rule<Iterator, std::vector<char>()> quoted_string;
};
int main() {
std::string input = "\"foo\\u0041\\U00000041\"";
typedef std::string::const_iterator iterator_type;
QuotedString<iterator_type> qs;
std::string result;
bool r = parse( input.cbegin(), input.cend(), qs, result );
std::cout << std::boolalpha << r << ": '" << result << "'\n";
}
打印:
true: 'foo[0x0041][0x00000041]'