Boost Spirit x3 解析器的属性类型为 std::vector<boost::variant<char, char>> 而不是 std::string

Boost Spirit x3 parser has attribute of type std::vector<boost::variant<char, char>> instead of std::string

上下文

我正在使用 Boost Spirit X3 从 Markdown 文件生成 HTML,如下所示:

// simplified code structure
auto str = x3::lexeme[+(x3::char_ - x3::eol)];

auto h1_action = [&](auto& ctx) { /* generate output */ };
auto h1 = ("# " > str)[h1_action];

auto p_action = [&](auto& ctx) { /* generate output */ };
auto p = (+(str > x3::eol))[p_action];

auto markdown = (h1 | p) % *x3::eol;

x3::phrase_parse(begin, end, markdown, x3::blank);

问题 1:符号表

我使用符号表来识别转义字符:

x3::symbols<char> esc;
esc.add
    ("\(", '(')
    ("\)", ')')
    /* ... */;

auto dummy_action = [&](auto& ctx) {
    auto value = x3::attr(ctx);
};

auto esc_str = (x3::lexeme[+(esc | (x3::char_ - x3::eol))])[dummy_action];
上面的 lambda dummy_action 中的

auto valuestd::vector<boost::variant<char, char>> 类型,根据 Compound Attribute Rules 它应该是 std::vector<char>,因此是仅仅是 string.

a: A, b: B --> (a | b): variant<A, B>
a: A, b: A --> (a | b): A

这些规则规定,如果替代解析器的两个参数(在上面的解析器 esc_str 中是 esc(x3::char_ - x3::eol))是相同类型的(char 在这种情况下),结果是 不是 变体。但它是,我想了解为什么。

问题 2:字符解析器

我想解析括号中的字符串,后跟 x3::eol,其中字符串本身也可以包含任意括号,例如(path/to/img(2).png)\n.

auto paran = x3::char_(')') >> !x3::eol;
auto ch = x3::char_ - (x3::eol | ')');
auto src = (x3::lexeme[x3::lit('(') >> +(paran | ch) >> ')'])[dummy_action];

同样的问题:我希望 src 的属性是 string 类型,因为替代解析器 [=32] 的两个参数 paranch =]有相同的属性,但是src的属性是std::vector<boost::variant<char, char>>.

结论

显然我可以简单地将结果转换为字符串,但是对于如何解析上述示例并直接接收字符串作为结果或解释为什么结果是 variant<char, char>。 (为什么您想要多次包含完全相同类型的变体?)

在此先感谢您的帮助!

编辑

Markdown 参考:我使用的是 this markdown reference,它没有指定底层语法,所以另外,我使用 Visual Studio 代码的 built-in markdown 预览功能来分析边缘情况.我知道我的 post 中的所有解析器都不正确或至少不完整(例如,无法识别空的 headers)。

语义操作:我知道关注点分离是一种更好的方法,即首先生成 AST/DOM 然后从中生成输出。我只是做不到,因为我只想解析一个非常有限的 markdown 子集,所以我选择了语义操作方法。

我同意属性合成可以......令人惊讶。

根本原因似乎是坚持对原始解析器表达式使用语义操作。通常的处理方法是

auto str
    = x3::rule<struct str_, std::string>{"str"}
    = x3::lexeme[+(esc | (x3::char_ - x3::eol))];

然后,如果必须的话,将 SA 附加到更高级别规则中的 str(您基本上已经在 p_action 中)。

第二题

好像和第一个差不多。

Same problem: I expect the attribute of src to be of type string, since the two arguments paran and ch of the alternative parser (paran | ch)

如果您希望 src 的属性是特定类型,您可能应该这样声明它:

auto eol = x3::eol | x3::eoi;
auto paran
    //= x3::rule<struct last_paren_, std::string> {"paran"} // aids in debug output
    = char_(')') >> !eol;
auto src
    = x3::rule<struct src_, std::string>{"src"}
    = lexeme['(' >> *(~char_("\r\n \t\b\f)") | paran) >> ')'];

观察

  1. 我质疑语法是否正确。特别是,您当前指定如何将括号嵌入超链接源规范中的方式与我所知道的降价引擎不匹配

    Including Whosebug's, as you can see. There was no need for the last parenthesis to be at EOL.

    如果您参考了您正在尝试实施的特定 Markdown 规范,我很乐意评估更多。

  2. 另外,我不相信在语义动作中完成所有繁重工作的方法(参见 Boost Spirit: "Semantic actions are evil"?)是有用的。

    一般来说,我建议将解析和输出生成的问题分开。这将使

    更容易
    • achieve/test 正确性
    • 维护解析器
    • 更改从已解析文档生成输出的方式

完整演示

这是一个将事物联系在一起的演示,同时仍然保持您当前的方法,强调语义操作。

希望所显示的改进和想法有所帮助。特别是在您学习或维护语法时,有条件启用的规则调试可能是一个很大的生产力加速器。

Live On Compiler Explorer

#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <fstream>
#include <iostream>
#include <boost/core/demangle.hpp>

namespace x3 = boost::spirit::x3;
namespace Parser {
    using x3::char_;
    using x3::lit;
    using x3::lexeme;
    // simplified code structure
#if 0
    auto str = lexeme[+(char_ - x3::eol)];
#else
    auto esc = [] {
        x3::symbols<char> esc;
        esc.add
            ("\(", '(')("\)", ')')
            ("\[", '[')("\]", ']')
        /* ... */;
        return esc;
    }();

    auto eol = x3::eol | x3::eoi;
    auto paran
        //= x3::rule<struct last_paren_, std::string> {"paran"} // aids in debug output
        = char_(')') >> !eol;
    auto src
        = x3::rule<struct src_, std::string>{"src"}
        = lexeme['(' >> *(~char_("\r\n \t\b\f)") | paran) >> ')'];

    auto hyperlink 
        = x3::rule<struct hyperlink_, std::string>{"hyperlink"}
        = '[' >> *(esc | ~char_("\r\n]")) >> ']' >> src;

    auto str
        = x3::rule<struct str_, std::string>{"str"}
        = lexeme[
            +( esc
            | &lit('[') >> hyperlink  // the &lit supresses verbose debug
            | (char_ - x3::eol)
            )];
#endif

    auto h1_action = [](auto &) { /* generate output */ };
    auto h1
        = x3::rule<struct h1_, std::string> {"h1"}
        = ("# " > str)[h1_action]
        ;

    auto p_action = [](auto &) { /* generate output */ };
    auto p
        = x3::rule<struct p_, std::string> {"p"}
        = (+(str > eol))[p_action];

    auto content
        = x3::rule<struct lines_, std::string> {"content"}
        = (h1 | p) % +x3::eol;

    auto markdown = x3::skip(x3::blank)[*x3::eol >> content];
} // namespace Parser

int main() {
#if 0
    std::ifstream ifs("input.txt");
    std::string const s(std::istreambuf_iterator<char>(ifs), {});
#else
    std::string const s = R"(
# Frist

This [introduction](https://en.wikipedia.org/wiki/Wikipedia:Introduction_(historical))
serves no purpose. Other than to show some [[hyper\]links](path/to/img(2).png)
)";
#endif

    parse(begin(s), end(s), Parser::markdown);
}

打印调试输出:

<content>
  <try># Frist\n\n    This [i</try>
  <h1>
    <try># Frist\n\n    This [i</try>
    <str>
      <try>Frist\n\n    This [int</try>
      <success>\n\n    This [introduc</success>
      <attributes>[F, r, i, s, t]</attributes>
    </str>
    <success>\n\n    This [introduc</success>
  </h1>
  <h1>
    <try>This [introduction](</try>
    <fail/>
  </h1>
  <p>
    <try>This [introduction](</try>
    <str>
      <try>This [introduction](</try>
      <hyperlink>
        <try>[introduction](https</try>
        <src>
          <try>(https://en.wikipedi</try>
          <success>\n    serves no purpo</success>
          <attributes>[h, t, t, p, s, :, /, /, e, n, ., w, i, k, i, p, e, d, i, a, ., o, r, g, /, w, i, k, i, /, W, i, k, i, p, e, d, i, a, :, I, n, t, r, o, d, u, c, t, i, o, n, _, (, h, i, s, t, o, r, i, c, a, l, )]</attributes>
        </src>
        <success>\n    serves no purpo</success>
        <attributes>[i, n, t, r, o, d, u, c, t, i, o, n, h, t, t, p, s, :, /, /, e, n, ., w, i, k, i, p, e, d, i, a, ., o, r, g, /, w, i, k, i, /, W, i, k, i, p, e, d, i, a, :, I, n, t, r, o, d, u, c, t, i, o, n, _, (, h, i, s, t, o, r, i, c, a, l, )]</attributes>
      </hyperlink>
      <success>\n    serves no purpo</success>
      <attributes>[T, h, i, s,  , i, n, t, r, o, d, u, c, t, i, o, n, h, t, t, p, s, :, /, /, e, n, ., w, i, k, i, p, e, d, i, a, ., o, r, g, /, w, i, k, i, /, W, i, k, i, p, e, d, i, a, :, I, n, t, r, o, d, u, c, t, i, o, n, _, (, h, i, s, t, o, r, i, c, a, l, )]</attributes>
    </str>
    <str>
      <try>    serves no purpos</try>
      <hyperlink>
        <try>[[hyper\]links](path</try>
        <src>
          <try>(path/to/img(2).png)</try>
          <success>\n    </success>
          <attributes>[p, a, t, h, /, t, o, /, i, m, g, (, 2, ), ., p, n, g]</attributes>
        </src>
        <success>\n    </success>
        <attributes>[[, h, y, p, e, r, ], l, i, n, k, s, p, a, t, h, /, t, o, /, i, m, g, (, 2, ), ., p, n, g]</attributes>
      </hyperlink>
      <success>\n    </success>
      <attributes>[s, e, r, v, e, s,  , n, o,  , p, u, r, p, o, s, e, .,  , O, t, h, e, r,  , t, h, a, n,  , t, o,  , s, h, o, w,  , s, o, m, e,  , [, h, y, p, e, r, ], l, i, n, k, s, p, a, t, h, /, t, o, /, i, m, g, (, 2, ), ., p, n, g]</attributes>
    </str>
    <str>
      <try>    </try>
      <fail/>
    </str>
    <success>    </success>
  </p>
  <success>    </success>
</content>