簡體   English   中英

Boost Spirit Qi驗證輸入解析器

[英]Boost Spirit Qi validating input parser

我有一個非常基本的Boost Spirit Qi語法,可以解析IP端口或IP端口范圍,即"6322""6322-6325"

語法如下:

  template<class It>
  void init_port_rule(u16_rule<It>& port)
  {
    port = boost::spirit::qi::uint_parser<uint16_t, 10, 2, 5>();
    port.name("valid port range: (10, 65535)");
  }

  typedef boost::fusion::vector
    < std::uint16_t
    , boost::optional<std::uint16_t>
    > port_range_type
  ;

  template<class It>
  struct port_range_grammar
    : boost::spirit::qi::grammar
      < It
      , port_range_type()
      >
  {
    typedef typename port_range_grammar::base_type::sig_type signature;

    port_range_grammar()
      : port_range_grammar::base_type(start, "port_range")
    {
      init_port_rule(port);
      using namespace boost::spirit::qi;
      start = port > -(lit("-") > port);
    }

  private:
    boost::spirit::qi::rule<It, signature> start;
    boost::spirit::qi::rule<It, std::uint16_t()> port;
  };

我有點定義, port1必須小於port2 我想我必須在這里使用eps解析器,但似乎找不到合適的方法來指定它。 任何建議都非常歡迎。

好吧,我想我已經知道了...

port_range_grammar()
  : port_range_grammar::base_type(start, "port_range")
{
  init_port_rule(port);
  using namespace boost::spirit::qi;
  namespace pnx = boost::phoenix;
  namespace fus = boost::fusion;
  start = port > -(lit("-") > port)
               > eps( pnx::bind
                       ( [](auto const& parsed)
                         {
                           if(!fus::at_c<1>(parsed).is_initialized())
                             return true;

                           auto lhs = fus::at_c<0>(parsed);
                           auto rhs = *fus::at_c<1>(parsed);
                           return lhs < rhs;
                         }
                       , _val
                       )
                    )
  ;
}

想法是將解析后的值傳遞給eps解析器,后者將檢查所構造的port_range_type是否具有小於第二元素的第一元素。

您確實可以使用語義動作。 但是,您不一定總是需要將它們附加到eps節點。 如果您這樣做,將會得到以下結果:

port %= uint_parser<uint16_t, 10, 2, 5>() >> eps[ _pass = (_val>=10 && _val<=65535) ];
start = (port >> -('-' >> port)) >> eps(validate(_val));

請注意,一個規則使用帶有語義動作的簡單表單eps 這要求operator%= 仍調用自動屬性傳播

第二個實例使用eps語義謂詞形式 validate函數必須是Phoenix Actor,我將其定義為:

struct validations {
    bool operator()(PortRange const& range) const {
        if (range.end)
            return range.start<*range.end;
        return true;
    }
};
boost::phoenix::function<validations> validate;

更通用/一致

請注意,您可以在兩個規則上都使用第二種規則樣式,如下所示:

port %= uint_parser<Port, 10, 2, 5>() >> eps(validate(_val));
start = (port >> -('-' >> port))      >> eps(validate(_val));

如果您只是添加重載以驗證單個端口,則:

struct validations {
    bool operator()(Port const& port) const {
        return port>=10 && port<=65535;
    }
    bool operator()(PortRange const& range) const {
        if (range.end)
            return range.start<*range.end;
        return true;
    }
};

初試

讓我們定義一些不錯的邊緣案例並對其進行測試!

生活在Coliru

#include <boost/fusion/adapted/struct.hpp>
#include <boost/optional/optional_io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;

using Port = std::uint16_t;

struct PortRange {
    Port start;
    boost::optional<Port> end;
};

BOOST_FUSION_ADAPT_STRUCT(PortRange, start, end)

template <class It, typename Attr = PortRange> struct port_range_grammar : qi::grammar<It, Attr()> {

    port_range_grammar() : port_range_grammar::base_type(start, "port_range") {
        using namespace qi;

        port %= uint_parser<Port, 10, 2, 5>() >> eps(validate(_val));
        start = (port >> -('-' >> port))      >> eps(validate(_val));

        port.name("valid port range: (10, 65535)");
    }

  private:
    struct validations {
        bool operator()(Port const& port) const {
            return port>=10 && port<=65535;
        }
        bool operator()(PortRange const& range) const {
            if (range.end)
                return range.start<*range.end;
            return true;
        }
    };
    boost::phoenix::function<validations> validate;
    qi::rule<It, Attr()> start;
    qi::rule<It, Port()> port;
};

int main() {
    using It = std::string::const_iterator;
    port_range_grammar<It> const g;

    std::string const valid[]   = {"10", "6322", "6322-6325", "65535"};
    std::string const invalid[] = {"9", "09", "065535", "65536", "-1", "6325-6322"};

    std::cout << " -------- valid cases\n";
    for (std::string const input : valid) {
        It f=input.begin(), l = input.end();
        PortRange range;
        bool accepted = parse(f, l, g, range);
        if (accepted)
            std::cout << "Parsed '" << input << "' to " << boost::fusion::as_vector(range) << "\n";
        else
            std::cout << "TEST FAILED '" << input << "'\n";
    }

    std::cout << " -------- invalid cases\n";
    for (std::string const input : invalid) {
        It f=input.begin(), l = input.end();
        PortRange range;
        bool accepted = parse(f, l, g, range);
        if (accepted)
            std::cout << "TEST FAILED '" << input << "' (returned " << boost::fusion::as_vector(range) << ")\n";
    }
}

打印:

 -------- valid cases
Parsed '10' to (10 --)
Parsed '6322' to (6322 --)
Parsed '6322-6325' to (6322  6325)
Parsed '65535' to (65535 --)
 -------- invalid cases
TEST FAILED '065535' (returned (6553 --))

恭喜我們發現一個破損的情況

事實證明,通過將uint_parser限制在5個位置,我們可以在輸入中保留字符,以便065535解析為6553 (不解析為'5' ...)。 修復很簡單:

start = (port >> -('-' >> port)) >> eoi >> eps(validate(_val));

或者確實是:

start %= (port >> -('-' >> port)) >> eoi[ _pass = validate(_val) ];

固定版本Live On Coliru

關於屬性類型的幾句話

您會注意到我修改了您的屬性類型。 這大部分是“好味道”。 注意,實際上,您可能希望將范圍表示為單端口或范圍:

using Port = std::uint16_t;

struct PortRange {
    Port start, end;
};

using PortOrRange = boost::variant<Port, PortRange>;

然后您將解析為:

port %= uint_parser<Port, 10, 2, 5>() >> eps(validate(_val));
range = (port >> '-' >> port)         >> eps(validate(_val));

start = (range | port) >> eoi;

完整演示Live On Coliru

您可能會認為這會變得很笨拙。 我同意!

簡化

首先讓我們不要使用variantoptional 讓我們將單個端口設置恰好具有start==end

using Port = std::uint16_t;

struct PortRange {
    Port start, end;
};

像這樣解析:

start = port >> -('-' >> port | attr(0)) >> eoi >> eps(validate(_val));

我們在validate所做的只是檢查end是否為0

    bool operator()(PortRange& range) const {
        if (range.end == 0) 
            range.end = range.start;
        return range.start <= range.end;
    }

現在的輸出是: Live On Coliru

 -------- valid cases
Parsed '10' to (10-10)
Parsed '6322' to (6322-6322)
Parsed '6322-6325' to (6322-6325)
Parsed '65535' to (65535-65535)
 -------- invalid cases

請注意,現在如何始終枚舉start .. end而不知道是否存在端口或端口范圍。 這可能很方便(在某種程度上取決於您要實現的邏輯)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM