【问题标题】:Boost Spirit: Sub-grammar appending to string?Boost Spirit:附加到字符串的子语法?
【发布时间】:2015-11-24 06:15:33
【问题描述】:

我在玩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 被添加到QuotedStringstd::string 属性(A0x41 )。

但我当然需要为 0x7f 以外的任何内容生成一个 sequence 字符(字节)。 EscapedUnicode 需要生成一个std::string,它必须附加QuotedString 生成的字符串中。

这就是我遇到障碍的地方。我不明白 Boost.Spirit 与 Boost.Phoenix 协同工作时所做的事情,并且我所做的任何尝试都会导致冗长且几乎无法解读的与模板相关的编译器错误。

那么,我该怎么做呢?答案实际上不需要进行正确的 Unicode 转换;这是std::string 的问题,我需要一个解决方案。

【问题讨论】:

  • “但我当然需要为 0x7f 以外的任何内容生成一个字符序列(字节)。” - 你想要什么编码?
  • 就像评论一样:一般来说,当您有诸如“为什么不使用我的序列附加/连接/做我想做的事情”之类的问题时,请查看备忘单:boost.org/doc/libs/1_58_0/libs/spirit/doc/html/spirit/qi/…

标签: c++11 boost boost-spirit boost-spirit-qi


【解决方案1】:

应用几点:

  • 请不要掩盖 using namespace 涉及高度通用的代码。 ADL 会毁了你的一天,除非你能控制它
  • 运算符%=自动规则分配,这意味着即使存在语义动作也会强制进行自动属性传播。您不希望这样,因为如果您想编码为多字节字符串表示,uint_parser 公开的属性将不会(正确地)自动传播。
  • 输入字符串

    std::string input = "\"foo\u0041\"";
    

    需要

    std::string input = "\"foo\\u0041\"";
    

    否则编译器会在解析器运行之前进行转义处理:)

以下是任务内容的具体技巧:

  • 您需要将规则的声明属性更改为 Spirit 会以简单序列自动“展平”的内容。例如

    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]'

【讨论】:

  • 从不使用using namespace,除非尝试将一长串明确的using boost::spirit::qi::uint_parser 等简化为尽可能少的LoC。 ;-) 我想我需要一些时间来思考你答案的“直截了​​当”的部分(因为正是凤凰城的东西仍然让我头疼)。但它有效,非常感谢您的详尽回答。
  • 老实说,我认为由其他细微的事情(如%=)引起的嘈杂错误很可能会破坏聚会的直接部分。如果你展示你被困住的东西,如果你愿意,我可以发现问题
  • 最终结果与我最初的想法(以及您在此处演示的内容)完全不同。我把它贴在codereview.SE,希望能解决剩下的不太好的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多