我有一个非常基本的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
。我想我必须在这里使用{{1}}解析器,但似乎没有找到指定它的正确方法。任何建议都非常受欢迎。
答案 0 :(得分:2)
好的,我想我已经弄清楚了......
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
是否第一个元素小于第二个元素。
答案 1 :(得分:1)
您确实可以使用语义操作。但是,您并不总是需要将它们附加到eps
节点。以下是您的所得:
port %= uint_parser<uint16_t, 10, 2, 5>() >> eps[ _pass = (_val>=10 && _val<=65535) ];
start = (port >> -('-' >> port)) >> eps(validate(_val));
请注意,一条规则使用附加了语义操作的Simple Form eps
。这需要operator%=
到still invoke automatic attribute propagation。
第二个实例使用Semantic Predicate form of eps
。 validate
函数需要是凤凰演员,我将它定义为:
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;
}
};
让我们定义一些不错的边缘情况并测试它们!
<强> Live On 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
您会注意到我修改了您的属性类型。其中大部分都是&#34;良好的品味&#34;。请注意,在实践中,您可能希望将范围表示为单端口或范围:
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
你可能会认为这会变得非常不适合使用。 我同意!
首先,请不要使用variant
或optional
。让我们创建一个单独的端口只是一个范围恰好有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
。这可能很方便(取决于你正在实施的逻辑)。