Boost Spirit x3 -- 使用其他解析器参数化解析器

Posted

技术标签:

【中文标题】Boost Spirit x3 -- 使用其他解析器参数化解析器【英文标题】:Boost Spirit x3 -- Parameterizing Parsers with other Parsers 【发布时间】:2021-05-12 20:41:37 【问题描述】:

我没有太多代码可以展示这个,因为我没有设法让任何工作,但高级问题是我正在尝试为一个家庭创建一系列解析器相关语言。我的意思是这些语言将共享许多相同的结构,但不会完全重叠。举个简单的例子,假设我有一个 AST,它由一些(在本例中完全人为设计的)“叶子”类型参数化:

template <typename t>
struct fooT 
  std::string name;
  t leaf;
;

一种语言可能将t 实例化为int,而另一种语言则实例化为double。我想做的是创建一个模板类或可以用不同的t 和相应的解析器规则实例化的东西,这样我就可以生成一系列组合解析器。

在我的真实示例中,我有一堆嵌套结构,它们在各种语言中都是相同的,但在 AST 的最边缘只有几个小的变化,所以如果我不能很好地组合解析器,我最终会复制一堆解析规则、AST 节点等。实际上我已经通过 not 将它放在一个类中并非常仔细地安排我的头文件和导入来让它工作,以便我可以使用可以组装的特殊名称的“悬空”解析器规则。这样做的一个缺点是我不能在同一个程序中包含多种不同语言的解析器——正是因为出现了名称冲突。

有人知道我该如何解决这个问题吗?

【问题讨论】:

【参考方案1】:

X3 的好处在于您可以像定义解析器一样轻松地生成解析器。

例如

template <typename T> struct AstNode 
    std::string name;
    T leaf;
;

现在让我们定义一个通用解析器生成器:

namespace Generic 
    template <typename T> auto leaf = x3::eps(false);

    template <> auto leaf<int>
        = "0x" >> x3::int_parser<uintmax_t, 16>;
    template <> auto leaf<std::string>
        = x3::lexeme['"' >> *~x3::char_('"') >> '"'];

    auto no_comment = x3::space;
    auto hash_comments = x3::space |
        x3::lexeme['#' >> *(x3::char_ - x3::eol)] >> (x3::eol | x3::eoi);
    auto c_style_comments = x3::space |
        "/*" >> x3::lexeme[*(x3::char_ - "*/")] >> "*/";
    auto cxx_style_comments = c_style_comments |
        x3::lexeme["//" >> *(x3::char_ - x3::eol)] >> (x3::eol | x3::eoi);

    auto name = leaf<std::string>;

    template <typename T> auto parseNode(auto heading, auto skipper) 
        return x3::skip(skipper)[
            x3::as_parser(heading) >> name >> ":" >> leaf<T>
        ];
    

这允许我们用不同的叶子类型和船长样式来组合各种语法:

namespace Language1 
    static auto const grammar =
        Generic::parseNode<int>("value", Generic::no_comment);


namespace Language2 
    static auto const grammar =
        Generic::parseNode<std::string>("line", Generic::cxx_style_comments);

让我们演示一下:

Live On Coliru

#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted.hpp>
#include <iomanip>
namespace x3 = boost::spirit::x3;

template <typename T> struct AstNode 
    std::string name;
    T leaf;
;

BOOST_FUSION_ADAPT_TPL_STRUCT((T), (AstNode)(T), name, leaf)

namespace Generic 
    template <typename T> auto leaf = x3::eps(false);

    template <> auto leaf<int>
        = "0x" >> x3::uint_parser<uintmax_t, 16>;
    template <> auto leaf<std::string>
        = x3::lexeme['"' >> *~x3::char_('"') >> '"'];

    auto no_comment = x3::space;
    auto hash_comments = x3::space |
        x3::lexeme['#' >> *(x3::char_ - x3::eol)] >> (x3::eol | x3::eoi);
    auto c_style_comments = x3::space |
        "/*" >> x3::lexeme[*(x3::char_ - "*/")] >> "*/";
    auto cxx_style_comments = c_style_comments |
        x3::lexeme["//" >> *(x3::char_ - x3::eol)] >> (x3::eol | x3::eoi);

    auto name = leaf<std::string>;

    template <typename T> auto parseNode(auto heading, auto skipper) 
        return x3::skip(skipper)[
            x3::as_parser(heading) >> name >> ":" >> leaf<T>
        ];
    


namespace Language1 
    static auto const grammar =
        Generic::parseNode<int>("value", Generic::no_comment);


namespace Language2 
    static auto const grammar =
        Generic::parseNode<std::string>("line", Generic::cxx_style_comments);


void test(auto const& grammar, std::string_view text, auto ast) 
    auto f = text.begin(), l = text.end();
    std::cout << "\nParsing: " << std::quoted(text, '\'') << "\n";
    if (parse(f, l, grammar, ast)) 
        std::cout << " -> name:" << ast.name << ",value:" << ast.leaf << "\n";
     else 
        std::cout << " -- Failed " << std::quoted(text, '\'') << "\n";
    


int main() 
    test(Language1::grammar, R"(value "one": 0x01)", AstNode<int>);
    test(
        Language2::grammar,
        R"(line "Hamlet": "There is nothing either good or bad, but thinking makes it so.")",
        AstNode<std::string>);

    test(
        Language2::grammar,
        R"(line // rejected: "Hamlet": "To be ..."
        "King Lear": /*hopefully less trite:*/"As flies to wanton boys are we to the gods")",
        AstNode<std::string>);

打印

Parsing: 'value "one": 0x01'
 -> name:one,value:1

Parsing: 'line "Hamlet": "There is nothing either good or bad, but thinking makes it so."'
 -> name:Hamlet,value:There is nothing either good or bad, but thinking makes it so.

Parsing: 'line // rejected: "Hamlet": "To be ..."
        "King Lear": /*hopefully less trite:*/"As flies to wanton boys are we to the gods"'
 -> name:King Lear,value:As flies to wanton boys are we to the gods

高级

对于高级场景(您需要跨翻译单元分离规则声明和定义和/或需要动态切换),您可以使用x3::any_rule&lt;&gt; 持有者。

【讨论】:

我确实将其拆分为多个翻译单元,因此我将研究 any_rule。这正是我所需要的。我试图嵌入一个班级,一切都很糟糕。谢谢! 再次感谢您的回答!我确实设法让我的解析器重构为使用这种风格。不幸的是,编译时间已经过去了!现在编译解析器大约需要 5 分钟和 10GB 内存。这里有人知道是否有解决此问题的好方法吗?我的特定语言有一个很大的递归变体,其中一些组件使用上述模板功能。 是的。这里通常的修复是重新引入 BOOST_SPIRIT_DEFINE 使用。如果没有,上下文类型似乎会受到整个解析器表达式的阻碍。这可能会导致针对不同上下文类型的实例化激增,尤其是在递归和/或跳过器更改的情况下。 如果您以某种方式显示代码,也许我可以尝试将其作为~驱魔~练习。我总有一天要学会穿那根针。 我可以尝试为您获取代码。我不确定我在技术上是否允许发布它,并且它也被拆分为大量文件。我确实在其他线程中看到了您的一些 cmets,并在可能的地方添加了 BOOST_SPIRIT_DEFINE 调用,但这并没有太大区别。问题是对于所有模板化解析器,我不知道如何使用 BOOST_SPIRIT_DEFINE 定义它们。我不得不承认我不是一个非常高级的 C++ 开发人员。

以上是关于Boost Spirit x3 -- 使用其他解析器参数化解析器的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 Boost Spirit X3 解析空的 C++ 结构

Boost.Spirit.X3 中的船长

Boost Spirit X3:跳过啥都不做的解析器

Boost Spirit X3的上下文是啥?

(如何)我可以在不安装完整的 boost 库的情况下使用 boost::spirit X3 吗?

使用 boost::spirit 解析任意精度整数