【问题标题】:Using boost::spirit defaulting a parsed value to an earlier value when parsing into a struct解析为结构时,使用 boost::spirit 将解析的值默认为较早的值
【发布时间】:2014-12-29 18:07:19
【问题描述】:

我通常熟悉使用 qi::attr 为解析输入中的缺失条目实现“默认值”。但是当需要从早期的解析中提取默认值时,我还没有看到如何执行此操作。

我正在尝试解析为以下结构:

struct record_struct {

    std::string Name;
    uint8_t Distance;
    uint8_t TravelDistance;
    std::string Comment;
};

从一个相对简单的“(text)(number)[(number)][//comment]”格式开始,其中第二个数字和注释都是可选的。如果第二个数字不存在,则其值应设置为与第一个数字相同。

下面是一个简化的工作代码示例,它不能完全按照我的意愿行事。这个版本只是默认为0 而不是正确的值。如果可能的话,我想将两个整数的解析隔离到一个单独的解析器规则中,而不放弃使用 fusion 结构。

我尝试过但没有编译的东西:

  • qi::attr(0) 替换为qi::attr(qi::_2)
  • 尝试使用语义操作 `qi::attr(0)[qi::_3 = qi::_2] 对 attr 匹配进行事后修改

完整的测试代码:

#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

【问题讨论】:

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


    【解决方案1】:

    首先,不要拼出omit[+space],而是使用skipper:

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

      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);
      
    3. 您可以完全使用 Boost Phoenix 并直接从语义操作中处理值

      Live On Coliru

      parsed = phrase_parse(iter, end, (
                     lexeme[+(alnum | '_')]
                  >> uint_ >> (uint_ | attr(phx::at_c<1>(_val)))
                  >> (("//" >> lexeme[+char_]) | attr(""))
              ), space, result);
      
    4. 您可以解析成optional&lt;uint8_t&gt; 并对信息进行后处理

      Live On Coliru

      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);
    

    这更准确,因为它不会解析不适合uint8_t 的无符号值。从上面混搭:Live On Coliru

    【讨论】:

    • 谢谢(再次)sehe...我在这里使用的语法在其他几个地方有语法上重要的空格,尽管这个特定的规则可能会切换到空格键。
    • @sehe 关于您的 Post Scriptum,我认为 jkerian 的意思类似于 this(尽管最好使用不修改结构的方法)。
    • @cv_and_he 感谢您的添加。我意识到这一点,但我认为没有有效的方法来做他所描述的事情,所以我会接受这个。我个人认为我的所有样本都非常可行,尽管我更喜欢 1(取决于应用程序,4)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多