Boost.Spirit.X3 中的船长

Posted

技术标签:

【中文标题】Boost.Spirit.X3 中的船长【英文标题】:Skippers in Boost.Spirit.X3 【发布时间】:2019-12-28 15:20:17 【问题描述】:

我正在尝试为该语言编写一个语法有点奇怪的解析器,并偶然发现了一个与skippers有关的问题,这让我认为我并不完全理解它们在Boost.Spirit.X3中是如何工作的。

问题在于,对于某些规则,EOL 是有意义的(即,我必须匹配行尾以确保语句正确),而对于其他规则则不是(因此可以跳过)。

因此,我决定将以下船长定义用于我的根规则:

namespace x3 = boost::spirit::x3;
namespace ch = x3::standard;

using ch::blank;
using x3::eol;

auto const skipper = comment | blank;

comment 显然只是跳过了 cmets。换句话说,我在输入流中保留了 EOL。

现在,对于另一条规则,我想使用如下定义:

auto const writable_property_declaration_def =
    skip(skipper | eol)
    [
        lit("#")
        > property_type
        > property_id
    ];

规则本身是另一个规则的一部分,实例化如下:

BOOST_SPIRIT_INSTANTIATE(property_declaration_type, iterator_type, context_type);

在哪里

using skipper_type = decltype(skipper);

using iterator_type = std::string::const_iterator;
using phrase_context_type = x3::phrase_parse_context<skipper_type>::type;
using error_handler_type = x3::error_handler<iterator_type>;
using context_type = x3::context<x3::error_handler_tag, std::reference_wrapper<error_handler_type>, phrase_context_type>;

这似乎不起作用:没有跳过 EOL。

现在,我的问题如下:

boost::spirit::x3::phrase_parse_context 和我使用的特定船长之间有什么联系? skip(p)[a] 究竟是如何工作的? 是否可以以某种方式定义底层规则,使其使用另一个船长,以便 X3 自行处理所有 EOL 而我不需要手动执行?

期待您的回复(-ies)! :)

【问题讨论】:

【参考方案1】:

您实际上并没有显示所有声明,因此设置的方式并不完全清楚。所以让我快速模拟一些东西:

Live On Wandbox

#define BOOST_SPIRIT_X3_DEBUG
#include <iomanip>
#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;
namespace P 
    using namespace x3;
    static auto const comment = lexeme [ 
            "/*" >> *(char_ - "*/") >> "*/"
          | "//" >> *~char_("\r\n") >> eol
        ];

    static auto const skipper = comment | blank;

    static auto const property_type = lexeme["type"];
    static auto const property_id = lexeme["id"];

    auto const demo =
        skip(skipper | eol) [
            lit("#")
            > property_type
            > property_id
        ];


int main() 
    for (std::string const input : 
            "#type id",
            "#type\nid",
        )
    
        std::cout << "==== " << std::quoted(input) << " ====" << std::endl;
        auto f = begin(input), l = end(input);
        if (parse(f, l, P::demo)) 
            std::cout << "Parsed successfully" << std::endl;
         else 
            std::cout << "Failed" << std::endl;
        

        if (f!=l) 
            std::cout << "Remaining input unparsed: " << std::quoted(std::string(f,l)) << std::endl;
        
    

如您所见,除非涉及到规则声明,否则实际上没有问题:

==== "#type id" ====
Parsed successfully
==== "#type
id" ====
Parsed successfully

让我们从这里放大

static auto const demo_def =
    skip(skipper | eol) [
        lit("#")
        > property_type
        > property_id
    ];

static auto const demo = x3::rule<struct demo_> "demo" = demo_def;

还可以:Live On Wandbox

<demo>
  <try>#type id</try>
  <success></success>
</demo>
<demo>
  <try>#type\nid</try>
  <success></success>
</demo>
Parsed successfully
==== "#type
id" ====
Parsed successfully

所以,我们知道x3::rule&lt;&gt; 实际上不是问题所在。这将是关于基于标签类型的静态调度(我认为,也就是规则 ID,在本例中为 struct demo_)。

直截了当:

static auto const demo_def =
    skip(skipper | eol) [
        lit("#")
        > property_type
        > property_id
    ];

static auto const demo = x3::rule<struct demo_> "demo";

BOOST_SPIRIT_DEFINE(demo)

还可以:Live On Wandbox

嗯,还有什么问题。也许如果有冲突的船长上下文?更换

    if (parse(f, l, P::demo)) 

    if (phrase_parse(f, l, P::demo, P::skipper)) 

还可以:Live On Wandbox

所以,也不是这样。好的,让我们尝试单独的实例化:

单独编译

Live On Wandbox

规则.h

#pragma once
#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/utility/error_reporting.hpp>

namespace x3 = boost::spirit::x3;
namespace P 
    using namespace x3;
    static auto const comment = lexeme [ 
            "/*" >> *(char_ - "*/") >> "*/"
          | "//" >> *~char_("\r\n") >> eol
        ];

    static auto const skipper = comment | blank;

    using demo_type = x3::rule<struct demo_>;
    extern demo_type const demo;

    BOOST_SPIRIT_DECLARE(demo_type)

规则.cpp

#include "rule.h"
#include <iostream>
#include <iomanip>

namespace P 
    using namespace x3;

    static auto const property_type = lexeme["type"];
    static auto const property_id = lexeme["id"];

    static auto const demo_def =
        skip(skipper | eol) [
            lit("#")
            > property_type
            > property_id
        ];

    struct demo_ 
        template<typename It, typename Ctx>
            x3::error_handler_result on_error(It f, It l, expectation_failure<It> const& ef, Ctx const&) const 
                std::string s(f,l);
                auto pos = std::distance(f, ef.where());

                std::cout << "Expecting " << ef.which() << " at "
                    << "\n\t" << s
                    << "\n\t" << std::setw(pos) << std::setfill('-') << "" << "^\n";

                return error_handler_result::fail;
            
    ;

    demo_type const demo "demo";
    BOOST_SPIRIT_DEFINE(demo)

    // for non-skipper invocation (x3::parse)
    using iterator_type = std::string::const_iterator;
    BOOST_SPIRIT_INSTANTIATE(demo_type, iterator_type, x3::unused_type)

    // for skipper invocation (x3::phrase_parse)
    using skipper_type = decltype(skipper);
    using phrase_context_type = x3::phrase_parse_context<skipper_type>::type;
    BOOST_SPIRIT_INSTANTIATE(demo_type, iterator_type, phrase_context_type)

test.cpp

#include "rule.h"
#include <iostream>
#include <iomanip>

int main() 
    std::cout << std::boolalpha;
    for (std::string const input : 
            "#type id",
            "#type\nid",
        )
    
        std::cout << "\n==== " << std::quoted(input) << " ====" << std::endl;

        
            auto f = begin(input), l = end(input);
            std::cout << "With top-level skipper: " << phrase_parse(f, l, P::demo, P::skipper) << std::endl;

            if (f!=l) 
                std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << std::endl;
            
        
        
            auto f = begin(input), l = end(input);
            std::cout << "Without top-level skipper: " << parse(f, l, P::demo) << std::endl;

            if (f!=l) 
                std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << std::endl;
            
        
    

打印预期:

==== "#type id" ====
With top-level skipper: <demo>
  <try>#type id</try>
  <success></success>
</demo>
true
Without top-level skipper: <demo>
  <try>#type id</try>
  <success></success>
</demo>
true

==== "#type
id" ====
With top-level skipper: <demo>
  <try>#type\nid</try>
  <success></success>
</demo>
true
Without top-level skipper: <demo>
  <try>#type\nid</try>
  <success></success>
</demo>
true

或者,不启用调试:

==== "#type id" ====
With top-level skipper: true
Without top-level skipper: true

==== "#type
id" ====
With top-level skipper: true
Without top-level skipper: true

最后的想法

很遗憾,也许我无法重现您描述的症状。但是,我希望上面的一些步骤确实阐明了规则定义的单独链接实际上是如何与船长/上下文相关的。

如果你的情况实际上更复杂,我只能想到另一种情况,X3 的情况可能不同于 QI 的情况。在 Qi 中,一条规则静态地声明了它的船长。在 X3 中,skipper 严格来自上下文(规则可以限制支持的skipper 数量的唯一方法是分离实例化并将定义隐藏在单独的TU 中)。

这意味着很容易意外地继承一个被覆盖的船长。这可能是违反直觉的,例如嵌套规则。如果您有不同的船长,我建议不要完全依赖继承的船长上下文。

【讨论】:

非常感谢您提供如此详细的分步分析!抱歉,我没有从我这边给你更多的背景信息。实际上,我的情况远比这复杂,我只是不知道什么是重要和相关的,什么不是。我将使用您提供的代码,然后再提出其他问题(如果有)或解决方案。但到现在为止,感觉就像我在一开始就错过了一些重要的事情,而问题肯定在我身边。 所以,我检查了所有代码示例,它们确实按预期工作。然后,我从每个 EOL 的所有变通方法和手动匹配中清理了我的代码库,并使用示例对其进行了相应更新。它有效!对于那些想知道的人来说,已经很难说问题出在哪里,因为我离我最初的实现变体还很远。最有可能的是,我尝试匹配已经从输入流中消耗的 EOL。无论如何,@sehe,再次感谢! 干杯。感谢您让我们知道。这也很有帮助,因为有时人们只需要增强信心,以便下次再看事物:)

以上是关于Boost.Spirit.X3 中的船长的主要内容,如果未能解决你的问题,请参考以下文章

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

Boost Spirit X3的上下文是啥?

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

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

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

将 mysql 服务绑定到 PCF 中的船长服务器时出现 java.lang.IllegalStateException