如何从循环中构建 boost::spirit 规则?
how to build boost::spirit rule from a loop?
我想使用 boost spirit 定义一个物理单位解析器。解析器必须考虑单位和前缀。为此,我也有存储前缀和 SI 单位的地图,这些前缀和单位在下面用伪代码定义。
std::map<std::string,double> prefixes;
// prefix - milli
prefixes["m"]=1.0e-03;
// prefix - centi
prefixes["c"]=1.0e-02;
...
std::map<std::string,std::vector<int>> units;
// time - seconds
units["s"] = {1,0,0,0,0,0,0};
// length - meters
units["m"] = {0,1,0,0,0,0,0};
...
从用户可以决定定义新前缀和单位的意义上来说,这些地图(尤其是后一张)不是固定的。
我构建了以下语法来解析诸如 kg
或 cm
之类的字符串,并得到一个 std::pair<double,std::vector<int>>
作为输出,其中将包含与成功解析的字符串匹配的前缀和单元.
UnitParser.h
struct UnitParser : qi::grammar<std::string::const_iterator,std::pair<double,std::vector<int>>()>
{
UnitParser();
qi::rule<std::string::const_iterator,std::pair<double,std::vector<int>>()> prefixedUnitRule;
qi::rule<std::string::const_iterator,double()> prefixRule;
qi::rule<std::string::const_iterator,std::vector<int>()> unitRule;
};
及其对应的 cpp UnitParser.cpp:
UnitParser::UnitParser() : UnitParser::base_type(prefixedUnit)
{
using namespace qi;
using namespace phx;
prefixedUnitRule = prefixRule >> unitRule;
for (const auto& p : prefixes)
prefixRule = string(p.first)[_val=p.second] | prefixRule.copy()[_val=p.second];
for (const auto& p : units)
unitRule = string(p.first)[_val=p.second] | unitRule.copy()[_val=p.second];
}
此实现编译但产生错误结果。
我的 question/problem 如下,如何使用前缀和单位映射循环构建 prefixRule
和 unitRule
规则?
您似乎在寻找 qi::symbols<>
:
template <typename It>
struct parser : qi::grammar<It, parsed_unit()> {
parser() : parser::base_type(start) {
_prefix.add
("m", 1.0 * std::milli::num / std::milli::den)
("c", 1.0 * std::centi::num / std::centi::den)
("d", 1.0 * std::deci::num / std::deci::den)
("da", 1.0 * std::deca::num / std::deca::den)
("h", 1.0 * std::hecto::num / std::hecto::den)
("k", 1.0 * std::kilo::num / std::kilo::den);
_unit.add
("s", units::s)
("m", units::m);
start = _prefix >> _unit >> qi::eoi
| qi::attr(1.0) >> _unit >> qi::eoi;
}
private:
qi::symbols<char, std::reference_wrapper<units::unit const> > _unit;
qi::symbols<char, double> _prefix;
qi::rule<It, parsed_unit()> start;
};
使用 std::array
作为维度和一些特征的完整演示,以便我们可以有效地从对维度的引用转换为已解析的单元:
演示
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/spirit/include/qi.hpp>
#include <ratio>
namespace qi = boost::spirit::qi;
namespace units {
using unit = std::array<int, 6>;
static const unit s = {{ 1, 0, 0, 0, 0, 0} };
static const unit m = {{ 0, 1, 0, 0, 0, 0} };
}
namespace boost { namespace spirit { namespace traits {
template <> struct is_container<units::unit, void> : mpl::false_ { };
template <typename T>
struct assign_to_attribute_from_value<typename std::reference_wrapper<T const>, T, void> :
assign_to_attribute_from_value<T, T, void> { };
} } }
using parsed_unit = std::pair<double/* factor*/, units::unit/* dimension*/>;
template <typename It>
struct parser : qi::grammar<It, parsed_unit()> {
parser() : parser::base_type(start) {
_prefix.add
("m", 1.0 * std::milli::num / std::milli::den)
("c", 1.0 * std::centi::num / std::centi::den)
("d", 1.0 * std::deci::num / std::deci::den)
("da", 1.0 * std::deca::num / std::deca::den)
("h", 1.0 * std::hecto::num / std::hecto::den)
("k", 1.0 * std::kilo::num / std::kilo::den);
_unit.add
("s", units::s)
("m", units::m);
start = _prefix >> _unit >> qi::eoi
| qi::attr(1.0) >> _unit >> qi::eoi;
}
private:
qi::symbols<char, std::reference_wrapper<units::unit const> > _unit;
qi::symbols<char, double> _prefix;
qi::rule<It, parsed_unit()> start;
};
int main() {
using It = std::string::const_iterator;
parser<It> p;
for (std::string const input : {
"mm", "cm", "dm", "m", "dam", "hm", "km",
"ms", "cs", "ds", "s", "das", "hs", "ks",
})
{
std::cout << "--- Test: '" << input << "'\n";
auto f = input.begin(), l = input.end();
parsed_unit u;
bool ok = qi::phrase_parse(f, l, p, qi::space, u);
if (ok) {
std::cout << "Parsed: {";
std::copy(u.second.begin(), u.second.end(), std::ostream_iterator<int>(std::cout));
std::cout << "}, scale factor: e" << std::log10(u.first) << " (~" << u.first << ")\n";
}
else
std::cout << "Parse failed\n";
if (f != l)
std::cout << "Remaining input: '" << std::string(f,l) << "'\n";
}
}
版画
--- Test: 'mm'
Parsed: {010000}, scale factor: e-3 (~0.001)
--- Test: 'cm'
Parsed: {010000}, scale factor: e-2 (~0.01)
--- Test: 'dm'
Parsed: {010000}, scale factor: e-1 (~0.1)
--- Test: 'm'
Parsed: {010000}, scale factor: e0 (~1)
--- Test: 'dam'
Parsed: {010000}, scale factor: e1 (~10)
--- Test: 'hm'
Parsed: {010000}, scale factor: e2 (~100)
--- Test: 'km'
Parsed: {010000}, scale factor: e3 (~1000)
--- Test: 'ms'
Parsed: {100000}, scale factor: e-3 (~0.001)
--- Test: 'cs'
Parsed: {100000}, scale factor: e-2 (~0.01)
--- Test: 'ds'
Parsed: {100000}, scale factor: e-1 (~0.1)
--- Test: 's'
Parsed: {100000}, scale factor: e0 (~1)
--- Test: 'das'
Parsed: {100000}, scale factor: e1 (~10)
--- Test: 'hs'
Parsed: {100000}, scale factor: e2 (~100)
--- Test: 'ks'
Parsed: {100000}, scale factor: e3 (~1000)
我想使用 boost spirit 定义一个物理单位解析器。解析器必须考虑单位和前缀。为此,我也有存储前缀和 SI 单位的地图,这些前缀和单位在下面用伪代码定义。
std::map<std::string,double> prefixes;
// prefix - milli
prefixes["m"]=1.0e-03;
// prefix - centi
prefixes["c"]=1.0e-02;
...
std::map<std::string,std::vector<int>> units;
// time - seconds
units["s"] = {1,0,0,0,0,0,0};
// length - meters
units["m"] = {0,1,0,0,0,0,0};
...
从用户可以决定定义新前缀和单位的意义上来说,这些地图(尤其是后一张)不是固定的。
我构建了以下语法来解析诸如 kg
或 cm
之类的字符串,并得到一个 std::pair<double,std::vector<int>>
作为输出,其中将包含与成功解析的字符串匹配的前缀和单元.
UnitParser.h
struct UnitParser : qi::grammar<std::string::const_iterator,std::pair<double,std::vector<int>>()>
{
UnitParser();
qi::rule<std::string::const_iterator,std::pair<double,std::vector<int>>()> prefixedUnitRule;
qi::rule<std::string::const_iterator,double()> prefixRule;
qi::rule<std::string::const_iterator,std::vector<int>()> unitRule;
};
及其对应的 cpp UnitParser.cpp:
UnitParser::UnitParser() : UnitParser::base_type(prefixedUnit)
{
using namespace qi;
using namespace phx;
prefixedUnitRule = prefixRule >> unitRule;
for (const auto& p : prefixes)
prefixRule = string(p.first)[_val=p.second] | prefixRule.copy()[_val=p.second];
for (const auto& p : units)
unitRule = string(p.first)[_val=p.second] | unitRule.copy()[_val=p.second];
}
此实现编译但产生错误结果。
我的 question/problem 如下,如何使用前缀和单位映射循环构建 prefixRule
和 unitRule
规则?
您似乎在寻找 qi::symbols<>
:
template <typename It>
struct parser : qi::grammar<It, parsed_unit()> {
parser() : parser::base_type(start) {
_prefix.add
("m", 1.0 * std::milli::num / std::milli::den)
("c", 1.0 * std::centi::num / std::centi::den)
("d", 1.0 * std::deci::num / std::deci::den)
("da", 1.0 * std::deca::num / std::deca::den)
("h", 1.0 * std::hecto::num / std::hecto::den)
("k", 1.0 * std::kilo::num / std::kilo::den);
_unit.add
("s", units::s)
("m", units::m);
start = _prefix >> _unit >> qi::eoi
| qi::attr(1.0) >> _unit >> qi::eoi;
}
private:
qi::symbols<char, std::reference_wrapper<units::unit const> > _unit;
qi::symbols<char, double> _prefix;
qi::rule<It, parsed_unit()> start;
};
使用 std::array
作为维度和一些特征的完整演示,以便我们可以有效地从对维度的引用转换为已解析的单元:
演示
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/spirit/include/qi.hpp>
#include <ratio>
namespace qi = boost::spirit::qi;
namespace units {
using unit = std::array<int, 6>;
static const unit s = {{ 1, 0, 0, 0, 0, 0} };
static const unit m = {{ 0, 1, 0, 0, 0, 0} };
}
namespace boost { namespace spirit { namespace traits {
template <> struct is_container<units::unit, void> : mpl::false_ { };
template <typename T>
struct assign_to_attribute_from_value<typename std::reference_wrapper<T const>, T, void> :
assign_to_attribute_from_value<T, T, void> { };
} } }
using parsed_unit = std::pair<double/* factor*/, units::unit/* dimension*/>;
template <typename It>
struct parser : qi::grammar<It, parsed_unit()> {
parser() : parser::base_type(start) {
_prefix.add
("m", 1.0 * std::milli::num / std::milli::den)
("c", 1.0 * std::centi::num / std::centi::den)
("d", 1.0 * std::deci::num / std::deci::den)
("da", 1.0 * std::deca::num / std::deca::den)
("h", 1.0 * std::hecto::num / std::hecto::den)
("k", 1.0 * std::kilo::num / std::kilo::den);
_unit.add
("s", units::s)
("m", units::m);
start = _prefix >> _unit >> qi::eoi
| qi::attr(1.0) >> _unit >> qi::eoi;
}
private:
qi::symbols<char, std::reference_wrapper<units::unit const> > _unit;
qi::symbols<char, double> _prefix;
qi::rule<It, parsed_unit()> start;
};
int main() {
using It = std::string::const_iterator;
parser<It> p;
for (std::string const input : {
"mm", "cm", "dm", "m", "dam", "hm", "km",
"ms", "cs", "ds", "s", "das", "hs", "ks",
})
{
std::cout << "--- Test: '" << input << "'\n";
auto f = input.begin(), l = input.end();
parsed_unit u;
bool ok = qi::phrase_parse(f, l, p, qi::space, u);
if (ok) {
std::cout << "Parsed: {";
std::copy(u.second.begin(), u.second.end(), std::ostream_iterator<int>(std::cout));
std::cout << "}, scale factor: e" << std::log10(u.first) << " (~" << u.first << ")\n";
}
else
std::cout << "Parse failed\n";
if (f != l)
std::cout << "Remaining input: '" << std::string(f,l) << "'\n";
}
}
版画
--- Test: 'mm'
Parsed: {010000}, scale factor: e-3 (~0.001)
--- Test: 'cm'
Parsed: {010000}, scale factor: e-2 (~0.01)
--- Test: 'dm'
Parsed: {010000}, scale factor: e-1 (~0.1)
--- Test: 'm'
Parsed: {010000}, scale factor: e0 (~1)
--- Test: 'dam'
Parsed: {010000}, scale factor: e1 (~10)
--- Test: 'hm'
Parsed: {010000}, scale factor: e2 (~100)
--- Test: 'km'
Parsed: {010000}, scale factor: e3 (~1000)
--- Test: 'ms'
Parsed: {100000}, scale factor: e-3 (~0.001)
--- Test: 'cs'
Parsed: {100000}, scale factor: e-2 (~0.01)
--- Test: 'ds'
Parsed: {100000}, scale factor: e-1 (~0.1)
--- Test: 's'
Parsed: {100000}, scale factor: e0 (~1)
--- Test: 'das'
Parsed: {100000}, scale factor: e1 (~10)
--- Test: 'hs'
Parsed: {100000}, scale factor: e2 (~100)
--- Test: 'ks'
Parsed: {100000}, scale factor: e3 (~1000)