在解析为struct时,使用boost :: spirit将解析后的值默认为较早的值

时间:2014-12-29 18:07:20

标签: c++ boost-spirit boost-spirit-qi

我通常熟悉使用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)
  • 尝试在一个attr匹配事件后修改语义动作`qi :: attr(0)[qi :: _ 3 = 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

1 个答案:

答案 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 )。

接下来,您可以通过多种方式实现这一目标。

  1. 使用 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>是该本地属性的占位符
    • 将规则初始化为&#34;自动规则&#34; (docs),即qi::_a,以便语义操作不会否决属性传播
  2. 这里有一个古怪的混合体,你实际上并没有使用%=但只是引用一个外部变量;这通常是一个坏主意,但由于你的解析器不是递归/可重入的,你可以这样做

    <强> Live On Coliru

    locals<>
  3. 你可以完全使用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);
    
  4. 您可以解析为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>

  5. Post Scriptum

    我注意到这个有点晚了:

      

    如果可能的话,我希望将两个整数的解析分离为一个单独的解析器规则,而不必放弃使用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