您确实可以使用语义动作。不过,您并不总是需要将它们附加到一个 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%=
来
依然调用自动属性传播。
第二个实例使用了“
Semantic Predicate 形式的 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";
}
}
输出:
Parsed '10' to (10
Parsed '6322' to (6322
Parsed '6322-6325' to (6322 6325)
Parsed '65535' to (65535
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) ];
修复版本在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;
完整演示:在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;
}
现在的输出结果是:在Coliru上实时运行
Parsed '10' to (10-10)
Parsed '6322' to (6322-6322)
Parsed '6322-6325' to (6322-6325)
Parsed '65535' to (65535-65535)
请注意,您现在可以始终枚举
start
..
end
而无需知道是端口还是端口范围。这可能很方便(根据您实现的逻辑而有所不同)。