【问题标题】:Discarding parsed result after semantic action语义操作后丢弃解析结果
【发布时间】:2019-04-25 22:29:57
【问题描述】:

在 Boost.Spirit 中,只需执行以下操作即可从流中读取到 std::vector

#include<vector>
#include<boost/spirit/include/qi.hpp>
namespace sqi = boost::spirit::qi;
int main(){
        std::string const v_str = "AA BB CC";
        std::vector<std::string> v;
        auto it = begin(v_str);
        bool r = sqi::phrase_parse(it, end(v_str), 
                    (*sqi::lexeme[+sqi::char_("A-Z")]), sqi::space, v);
        assert( v.size() == 3  and v[2] == "CC" );
}

但是,由于输入格式的原因,我提前知道了元素的数量,并且我应该能够在向量中保留空间。 例如输入字符串为“3 AA BB CC”,可以预先分配三个元素。

问题是如何将这些额外信息传递给向量并优化后面的push_back(例如避免重新分配)。

我尝试的是在开始时解析一个整数,将语义操作与执行reserve 的位置相关联。

        std::string const v_str = "3 AA BB CC";
        std::vector<std::string> v;
        auto it = begin(v_str);
        bool r = sqi::phrase_parse(it, end(v_str), 
             sqi::int_[([&](int i){v.reserve(i);})] >> 
                (*sqi::lexeme[+sqi::char_("A-Z")]), sqi::space, v);

问题是在语义操作之后整数没有被忽略,从我的测试中我可以看到它试图在保留后将结果(示例中的3)推送到向量中。

另一种解决方法是向 phrase_parse 函数添加另一个参数,但这似乎有点过头了。

那么,如何在 Boost.Spirit 中解析某些内容,只执行语义操作而不将结果发送到 sink 变量?

即使可以做到这一点,我也不确定这是否是正确的方法。

【问题讨论】:

  • 我想你可以试试 qi::repeat() 来实现你所需要的。请参阅boost.org/doc/libs/1_70_0/libs/spirit/doc/html/spirit/qi/… 上的参考资料。
  • @drus,链接末尾的示例正是我正在寻找的,但出于某种原因,在我的情况下,第一个 char_[...] 解析器也以 str 结束,而不是仅用于语义动作。也许这是因为我使用了 lambda 而不是 phoenix 表达式。

标签: c++ boost-spirit boost-spirit-qi semantic-actions


【解决方案1】:

您可以创建在插​​入和解析相同文本两次时仅计算在内的假向量:

#include<vector>
#include<boost/spirit/include/qi.hpp>
namespace sqi = boost::spirit::qi;
struct fake_vector
{
    typedef std::string value_type;
    fake_vector() : counter(0) {}
    std::size_t end() const {return 0;};
    void insert(std::size_t, std::string){ ++counter; }

    std::size_t counter;
};
int main(){
        std::string const v_str = "AA BB CC";
        auto it = begin(v_str);
        fake_vector fv;
        bool r = sqi::phrase_parse(it, end(v_str), (*sqi::lexeme[+sqi::char_("A-Z")]), sqi::space, fv);
        assert(fv.counter == 3);
        std::vector<std::string> v;
        v.reserve(fv.counter);
        it = begin(v_str);
        r = sqi::phrase_parse(it, end(v_str), (*sqi::lexeme[+sqi::char_("A-Z")]), sqi::space, v);
        assert( v.size() == 3  and v[2] == "CC" );
}

【讨论】:

  • 虽然 Spirit 使用的是push_back(不是insert)。我要去看看这个。目前我有一个不那么可怕的解决方案。
  • 我也是这么想的,但是编译器想要insert(.end(), value)
  • 顺便说一句,我知道元素的数量。我想让保留调用成为解析器的一部分。
  • 唯一的优势似乎是insert(.end(), value())vectorlistdeque[multi]set (具有不同的语义--提示)一起使用。虽然push_back 仅适用于vectorlistdeque,但不适用于[multi]set。它可能通过boost::phoenix::insert 表达式boost.org/doc/libs/1_66_0/boost/phoenix/stl/container/… 调用
【解决方案2】:

好吧,我似乎不得不解构 Spirit 的简单设施并将所有转换为语义动作,这在途中产生了其他问题(例如,lexeme[+char_] 映射到 std::vector&lt;char&gt; 而不是被执行的 std::string .

{
    std::string const v_str = "AA BB CC";
    std::vector<std::string> v;
    auto it = begin(v_str);
    bool r = sqi::phrase_parse(it, end(v_str), 
        (*(sqi::lexeme[(+sqi::char_("A-Z"))][([&](auto&& s){v.emplace_back(begin(s), end(s));})])), sqi::space);
    assert( v.size() == 3);
    assert( v[2] == "CC" );
}
{
    std::string const v_str = "3 AA BB CC";
    std::vector<std::string> v;
    auto it = begin(v_str);
    bool r = sqi::phrase_parse(it, end(v_str), 
        sqi::int_[([&](int i){v.reserve(i);})] >> 
            (*(sqi::lexeme[(+sqi::char_("A-Z"))][([&](auto&& s){v.emplace_back(begin(s), end(s));})])), sqi::space);
    assert( v.size() == 3 );
    assert( v[2] == "CC" );
}

由于这修改了phrase_parse 的最后一个参数,我不妨放一个虚拟的int

【讨论】:

  • 不是说你应该这样做 (on the contrary),但你可以不改变输入:coliru.stacked-crooked.com/a/981e890c963b0acc
  • @sehe,对不起,如果我不清楚,恰好是输入具有输入前面的元素数量的情况。我想利用这一点。我非常同意语义动作的路径会导致痛苦,但在这种情况下 1)似乎我无法在没有语义动作的情况下利用优化(有人必须致电 reserve)和 2)语义动作有对对象的状态没有真正的影响,所以我认为原则上应该没问题。
  • @sehe,我并没有尝试使用属性来执行此操作,但并非没有问题stackoverflow.com/questions/55965469/…。 (另外,我使用了错误版本的 Spirit)
【解决方案3】:

感谢@sehe 和@drus 指向我的链接并找到关于qi::omit 的链接,我意识到我可以关联一个语义动作然后省略结果。

我必须处理的格式是多余的(大小与元素的数量是多余的),所以我必须在语义上在任何情况下省略一些东西。

    using namespace sqi;
    std::string const v_str = "3 AA BB CC";
    {
        std::vector<std::string> v;
        auto it = begin(v_str);
        bool r = sqi::phrase_parse(
            it, end(v_str), 
            omit[int_] >> *lexeme[+(char_-' ')],
            space, v
        );
        assert( v.size() == 3 and v[2] == "CC" );
    }

但这并不意味着我不能将省略的(冗余)部分用于优化目的或一致性检查。

    {
        std::vector<std::string> v;
        auto it = begin(v_str);
        bool r = sqi::phrase_parse(
            it, end(v_str), 
            omit[int_[([&](int n){v.reserve(n);})]] >> *lexeme[+(char_-' ')],
            space, v
        );
        assert( v.size() == 3 and v[2] == "CC" );
    }

我同意语义行为是邪恶的,但我认为只有当它们改变接收器对象的状态时。 可以说reserve 不会改变向量的状态。

事实上,这样我可以通过reserve 优化内存使用,也可以通过使用repeat 而不是无限的kleene* 来优化解析器的执行。 (Apparently repeat can be more efficient).

    {
        std::vector<std::string> v;
        auto it = begin(v_str);
        int n;
        bool r = sqi::phrase_parse(
            it, end(v_str), 
            omit[int_[([&](int nn){v.reserve(n = nn);})]] >> repeat(phx::ref(n))[lexeme[+(char_-' ')]],
            space, v
        );
        assert( n == v.size() and v.size() == 3 and v[2] == "CC" );
    }

(取消phx::ref 是基础,因为必须延迟对n 的评估)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-28
    • 1970-01-01
    • 1970-01-01
    • 2014-06-25
    • 2017-06-21
    相关资源
    最近更新 更多