我通常熟悉使用qi :: attr来实现"默认值"对于解析输入中缺少的条目。但是,当需要从早期的解析中提取默认值时,我还没有看到如何执行此操作。
我试图解析以下结构:
struct record_struct {
std::string Name;
uint8_t Distance;
uint8_t TravelDistance;
std::string Comment;
};
从一个相对简单的"(文本)(数字)[(数字)] [//评论]"格式,其中第二个数字和注释都是可选的。如果第二个号码不存在,则其值应设置为与第一个号码相同。
以下是工作代码的简化示例,该代码不能完成我想要的操作。此版本默认为0
而不是正确的值。如果可能的话,我想将两个整数的解析分离为一个单独的解析器规则,而不必放弃使用fusion结构。
我尝试过的东西尚未汇编:
qi::attr(0)
qi::attr(qi::_2)
完整的测试代码:
#include <string>
#include <cstdint>
#include <boost/spirit/include/qi.hpp>
struct record_struct {
std::string Name;
uint8_t Distance;
uint8_t TravelDistance;
std::string Comment;
};
BOOST_FUSION_ADAPT_STRUCT(
record_struct,
(std::string, Name)
(uint8_t, Distance)
(uint8_t, TravelDistance)
(std::string, Comment)
)
std::ostream &operator<<(std::ostream &o, const record_struct &s) {
o << s.Name << " (" << +s.Distance << ":" << +s.TravelDistance << ") " << s.Comment;
return o;
}
bool test(std::string s) {
std::string::const_iterator iter = s.begin();
std::string::const_iterator end = s.end();
record_struct result;
namespace qi = boost::spirit::qi;
bool parsed = boost::spirit::qi::parse(iter, end, (
+(qi::alnum | '_') >> qi::omit[+qi::space]
>> qi::uint_ >> ((qi::omit[+qi::space] >> qi::uint_) | qi::attr(0))
>> ((qi::omit[+qi::space] >> "//" >> +qi::char_) | qi::attr(""))
), result);
if (parsed) std::cout << "Parsed: " << result << "\n";
else std::cout << "Failed: " << std::string(iter, end) << "\n";
return parsed;
}
int main(int argc, char **argv) {
if (!test("Milan 20 22")) return 1;
if (!test("Paris 8 9 // comment")) return 1;
if (!test("London 5")) return 1;
if (!test("Rome 1 //not a real comment")) return 1;
return 0;
}
输出:
Parsed: Milan (20:22)
Parsed: Paris (8:9) comment
Parsed: London (5:0)
Parsed: Rome (1:0) not a real comment
输出我想看:
Parsed: Milan (20:22)
Parsed: Paris (8:9) comment
Parsed: London (5:5)
Parsed: Rome (1:1) not a real comment
答案 0 :(得分:2)
首先,不要拼出omit[+space]
,而只使用船长:
bool parsed = qi::phrase_parse(iter, end, (
qi::lexeme[+(alnum | '_')]
>> uint_ >> (uint_ | attr(0))
>> (("//" >> lexeme[+qi::char_]) | attr(""))
), qi::space, result);
此处,qi::space
是船长。 lexeme[]
避免跳过子表达式(请参阅 Boost spirit skipper issues )。
接下来,您可以通过多种方式实现这一目标。
使用 local 属性临时存储值:
<强> Live On Coliru 强>
rule<It, record_struct(), locals<uint8_t>, space_type> g;
g %= lexeme[+(alnum | '_')]
>> uint_ [_a = _1] >> (uint_ | attr(_a))
>> -("//" >> lexeme[+char_]);
parsed = phrase_parse(iter, end, g, space, result);
这需要
qi::rule
的{{1}}声明; qi::locals<uint8_t>
是该本地属性的占位符qi::_a
,以便语义操作不会否决属性传播这里有一个古怪的混合体,你实际上并没有使用%=
但只是引用一个外部变量;这通常是一个坏主意,但由于你的解析器不是递归/可重入的,你可以这样做
<强> Live On Coliru 强>
locals<>
你可以完全使用Boost Phoenix并从语义动作
中处理这些值<强> Live On Coliru 强>
parsed = phrase_parse(iter, end, (
lexeme[+(alnum | '_')]
>> uint_ [ phx::ref(dist_) = _1 ] >> (uint_ | attr(phx::ref(dist_)))
>> (("//" >> lexeme[+char_]) | attr(""))
), space, result);
您可以解析为parsed = phrase_parse(iter, end, (
lexeme[+(alnum | '_')]
>> uint_ >> (uint_ | attr(phx::at_c<1>(_val)))
>> (("//" >> lexeme[+char_]) | attr(""))
), space, result);
并对信息进行后期处理
<强> Live On Coliru 强>
optional<uint8_t>
我注意到这个有点晚了:
如果可能的话,我希望将两个整数的解析分离为一个单独的解析器规则,而不必放弃使用fusion结构。
嗯,当然你可以:
std::string name;
uint8_t distance;
boost::optional<uint8_t> travelDistance;
std::string comment;
parsed = phrase_parse(iter, end, (
lexeme[+(alnum | '_')]
>> uint_ >> -uint_
>> -("//" >> lexeme[+char_])
), space, name, distance, travelDistance, comment);
result = { name, distance, travelDistance? *travelDistance : distance, comment };
这一点更准确,因为它无法解析不适合rule<It, uint8_t(uint8_t)> def_uint8 = uint_parser<uint8_t>() | attr(_r1);
的无符号值。从上面混合和匹配: Live On Coliru