如何创建能够有条件地删除合成项的可选解析器

How to create an optional parser that is able to conditionally drop synthesized items

我正在尝试创建一个可选的解析器规则。根据第一个属性的值,我想有选择地发出一个数据。

例如,对于输入:

x,2,3
y,3,4
x,5,6

如果第一个字符是 y 那么该行应该被丢弃。否则将被处理。在这个例子中,如果第 3 个属性是 >= 4 那么它是真的。综合属性应为 std::pair<bool, unsigned int>,其中 unsigned int 值是第二个属性。 解析器是:

using namespace qi = boost::spirit::qi;
using Data = std::pair<bool, unsigned>;
BOOST_PHOENIX_ADAPT_FUNCTION(Data, make_pair, std::make_pair, 2);

class DataParser :
    public qi::grammar<
    std::string::iterator,
    boost::spirit::char_encoding::ascii,
    boost::spirit::ascii::space_type,
    std::vector<Data>()
    >
{
    qi::rule<iterator_type, encoding_type, bool()> type;
    qi::rule<iterator_type, encoding_type, bool()> side;
    // doesn't compile: qi::rule<iterator_type, encoding_type, boost::spirit::ascii::space_type, boost::optional<Data>()> line;
    qi::rule<iterator_type, encoding_type, boost::spirit::ascii::space_type, qi::locals<bool, unsigned, bool>, Data()> line;
    qi::rule<iterator_type, encoding_type, boost::spirit::ascii::space_type, sig_type> start;

public:
    DataParser()
        : base_type(start)
    {
        using namespace qi::labels;

        type = qi::char_[_val = _1 == 'x'];
        side = qi::int_[_val = _1 >= 4];
        line %= (qi::omit[type[_a = _1]] >> ',' >> qi::omit[qi::uint_[_b = _1]] >> ',' >> qi::omit[side[_c = _1]])[if_(_a)[_val = make_pair(_c, _b)]];
        // doesn't compile: line %= (qi::omit[type[_a = _1]] >> ',' >> qi::omit[qi::uint_[_b = _1]] >> ',' >> qi::omit[side[_c = _1]])[if_(_a)[_val = make_pair(_c, _b)].else_[_val = qi::unused]];
        // doesn't compile: line %= (type >> ',' >> qi::uint_ >> ',' >> side)[if_(_1)[_val = make_pair(_3, _2)]];
        // doesn't compile: line %= (type >> ',' >> qi::uint_ >> ',' >> side)[if_(_1)[_val = make_pair(_3, _2)].else_[_val = unused]];
        start = *line;
    }
};

我得到:[[false, 2], [false, 0], [true, 5]] 我想得到的地方:[[false, 2], [true, 5]](第二个条目应该被丢弃)。

我尝试将 boost::optional<Data> 用于 data 规则,并将 unused 分配给 _val 但没有任何效果。

使用已接受的答案解决问题后进行编辑

现在的新规则是:

using Data = std::pair<bool, unsigned>;
BOOST_PHOENIX_ADAPT_FUNCTION(Data, make_pair, std::make_pair, 2);

class DataParser :
    public qi::grammar<
        std::string::iterator,
        boost::spirit::char_encoding::ascii,
        boost::spirit::ascii::blank_type,
        std::vector<Data>()
    >
{
    using Items = boost::fusion::vector<bool, unsigned, bool>;

    qi::rule<iterator_type, encoding_type, bool()> type;
    qi::rule<iterator_type, encoding_type, bool()> side;
    qi::rule<iterator_type, encoding_type, boost::spirit::ascii::blank_type, Items()> line;
    qi::rule<iterator_type, encoding_type, boost::spirit::ascii::blank_type, sig_type> start;

public:
    DataParser()
        : base_type(start)
    {
        using namespace qi::labels;
        namespace px = boost::phoenix;

        type = qi::char_[_val = _1 == 'x'];
        side = qi::int_[_val = _1 >= 4];
        line = type >> ',' >> qi::uint_ >> ',' >> side;
        start = line[if_(_1)[px::push_back(_val, make_pair(_3, _2))]] % qi::eol;
    }
};

关键点是使用语义动作来决定是否应该使用先前规则的所有属性来添加合成属性,在这种情况下line

好的。您使用了很多 power-tools。但请记住,with great power comes....

特别是,qi::locals,凤凰,语义动作:它们都会使生活复杂化,所以只将它们用作最后​​的手段(或者当它们很自然地适合时,这种情况很少见¹)。

直接思考,

 start = *line;

 line = // ....

当你说

If the first character is a y then the line should be discarded. Otherwise it will be processed.

你可以这样表达直接:

 line = !qi::lit('y') >> // ...

或者,说明要接受的初学者:

 line = qi::omit[ qi::char_("xz") ] >> // ...

完成。

直接映射

在这里,我将通过 re-ordering 和 pair<unsigned, bool> 作弊,使其与输入顺序匹配。现在一切都开箱即用,没有“任何”魔法:

line   = !qi::lit('y') >> qi::omit[qi::alnum] >> ',' >> qi::int_ >> ',' >> side;
ignore = +(qi::char_ - qi::eol);

start = qi::skip(qi::blank) [ (line | ignore) % qi::eol ];

但是它会导致您注意到的虚假条目:Live On Compiler Explorer

Parsed: {(2, false), (0, false), (5, true)}

改善

现在您可以通过更改 eol 来解决问题,以便同时吃掉似乎不包含有效数据行的后续行。然而,它变得笨重,我们仍然有翻转这对成员的欲望。

所以,我认为 actrion 可以 派上用场:

  public:
    DataParser() : DataParser::base_type(start) {
        using namespace qi::labels;

        start  = qi::skip(qi::blank) [
              (qi::char_ >> ',' >> qi::uint_ >> ',' >> qi::int_) [
                  _pass = process(_val, _1, _2, _3) ]
            % qi::eol ];
    }

  private:
    struct process_f {
        template <typename... T>
        bool operator()(Datas& into, char id, unsigned type, int side) const {
            switch(id) {
                case 'z': case 'x':
                    into.emplace_back(side >= 4, type);
                    break;
                case 'y': // ignore
                    break;
                case 'a':
                    return false; // fail the rule
            }
            return true;
        }
    };

    boost::phoenix::function<action_f> process;

你可以看到,现在有一个很好的关注点分离。您解析 (char,int,int) 并有条件地处理它。与您的尝试相比,这就是使它相对简单的原因。

现场演示

Live On Compiler Explorer

#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <fmt/ranges.h>
namespace qi = boost::spirit::qi;

using Data = std::pair<bool, unsigned>;
using Datas = std::vector<Data>;

template <typename It>
class DataParser : public qi::grammar<It, Datas()> {
    using Skipper = qi::blank_type;
    qi::rule<It, Datas(), Skipper> line;
    qi::rule<It, Datas()> start;

  public:
    DataParser() : DataParser::base_type(start) {
        using namespace qi::labels;

        start  = qi::skip(qi::blank) [
              (qi::char_ >> ',' >> qi::uint_ >> ',' >> qi::int_) [
                  _pass = process(_val, _1, _2, _3) ]
            % qi::eol ];
    }

  private:
    struct process_f {
        template <typename... T>
        bool operator()(Datas& into, char id, unsigned type, int side) const {
            switch(id) {
                case 'z': case 'x':
                    into.emplace_back(side >= 4, type);
                    break;
                case 'y': // ignore
                    break;
                case 'a':
                    return false; // fail the rule
            }
            return true;
        }
    };

    boost::phoenix::function<process_f> process;
};

int main() {
    using It = std::string::const_iterator;
    DataParser<It> p;

    for (std::string const input : {
            "x,2,3\ny,3,4\nx,5,6", 
            })
    {
        auto f = begin(input), l = end(input);
        Datas d;
        auto ok = qi::parse(f, l, p, d);

        if (ok) {
            fmt::print("Parsed: {}\n", d);
        } else {
            fmt::print("Parsed failed\n", d);
        }

        if (f!=l) {
            fmt::print("Remaining unparsed: '{}'\n", std::string(f,l));
        }
    }
}

版画

Parsed: {(false, 2), (true, 5)}

¹ Boost Spirit: "Semantic actions are evil"?