语义动作后丢弃解析结果

Discarding parsed result after semantic action

在 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" );
}

不过碰巧因为输入格式的缘故,我提前知道了元素个数,应该可以预留vector中的space。 比如输入字符串是“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 中的某些内容并只执行语义操作而不将结果发送到接收器变量?

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

您可以创建假向量,它只会在插入和解析相同文本两次时计算在内:

#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 的简单设施并将所有转换为语义动作,这在途中产生了其他问题(例如 lexeme[+char_] 映射到 std::vector<char>预期 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.

感谢@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" );
    }

(unsing phx::ref 是基本的,因为 n 的评估必须延迟)