约束现有的 Boost.Spirit real_parser(带有策略)

Posted

技术标签:

【中文标题】约束现有的 Boost.Spirit real_parser(带有策略)【英文标题】:Constraining the existing Boost.Spirit real_parser (with a policy) 【发布时间】:2015-05-21 13:51:07 【问题描述】:

我想解析一个浮点数,但不允许 NaN 值,所以我生成了一个继承自默认策略的策略并用它创建一个 real_parser

// using boost::spirit::qi::real_parser,real_policies,
//                           phrase_parse,double_,char_;

template <typename T>
struct no_nan_policy : real_policies<T>

    template <typename I, typename A>
    static bool
    parse_nan(I&, I const&, A&) 
          return false;
        
;

real_parser<double, no_nan_policy<double> > no_nan;

// then I can use no_nan to parse, as in the following grammar
bool ok = phrase_parse(first, last, 
   no_nan[ref(valA) = _1] >> char_('@') >> double_[ref(b) = _1],
space);

但现在我要确保用no_nan解析的字符串的总长度不超过4,即“1.23”或“.123”甚至“2.e6”或“inf”是可以的,“3.2323”不是,“nan”也不是。我不能在政策的parse_n/parse_frac_n 部分执行此操作,该部分分别查看点的左/右并且无法通信(...干净地),他们会有因为整体长度是相关的。

当时的想法是扩展real_parser(在boost/spirit/home/qi/numeric/real.hpp 中)并包装parse 方法——但是这个类没有方法。 real_parser 旁边是 any_real_parser 结构,确实parse,但这两个结构似乎没有任何明显的交互方式。

有没有一种方法可以轻松地注入我自己的 parse(),进行一些预检查,然后调用 real 解析 (return boost::spirit::qi::any_real_parser&lt;T, RealPolicy&gt;::parse(...)),然后遵循给定的策略?编写一个新的解析器是不得已的方法,但我希望有更好的方法。

(使用 Boost 1.55,即 Spirit 2.5.2,与 C++11)

【问题讨论】:

给定你想要的这个节点的规则集,听起来它本身就是一种迷你语言。为它定义一个语法怎么样,然后看看如何compose grammars? @AmiTavory 会非常缓慢和乏味 好吧,只要它在某些方面很棒......虽然说真的,这是一个有趣的断言,但如果你能解释为什么会很好(尤其是“慢”部分)。 @AmiTavory 当然。今天来回答这个问题 好的,谢谢 - 这是一个有趣的问题。 【参考方案1】:

看来我已经很接近了,即只需对 double_ 解析器进行一些更改,我就完成了。这可能比添加新语法更易于维护,因为所有其他解析都是以这种方式完成的。 – 携带7 hours ago

更易于维护的是根本不编写另一个解析器。

您基本上想要解析一个浮点数(Spirit 已经涵盖了您),但之后应用了一些验证。我会在语义操作中进行验证:

raw [ double_ [_val = _1] ] [ _pass = !isnan_(_val) && px::size(_1)<=4 ]

就是这样。

说明

解剖学:

double_ [_val = _1] 解析一个 double 并照常将其分配给暴露的属性¹ raw [ parser ] 匹配封闭的 parser 将原始源迭代器范围作为属性公开

[ _pass = !isnan_(_val) &amp;&amp; px::size(_1)&lt;=4 ] - 业务部分!

此语义操作附加到 raw[] 解析器。因此

_1 现在指的是已经解析了 double_ 的原始迭代器范围 _val 已经包含 double_ 成功匹配的“熟”值 _pass 是 Spirit 上下文标志,我们可以将其设置为 false 以使解析失败。

现在唯一剩下的就是将它们捆绑在一起。让我们做一个延迟版本的::isnan

boost::phoenix::function<decltype(&::isnan)> isnan_(&::isnan);

我们可以出发了。

测试程序

Live On Coliru

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <cmath>
#include <iostream>

int main ()

    using It = std::string::const_iterator;

    auto my_fpnumber = []  // TODO encapsulate in a grammar struct
        using namespace boost::spirit::qi;
        using boost::phoenix::size;

        static boost::phoenix::function<decltype(&::isnan)> isnan_(&::isnan);

        return rule<It, double()> (
                raw [ double_ [_val = _1] ] [ _pass = !isnan_(_val) && size(_1)<=4 ]
            );
    ();

    for (std::string const s:  "1.23", ".123", "2.e6", "inf", "3.2323", "nan" )
    
        It f = s.begin(), l = s.end();

        double result;
        if (parse(f, l, my_fpnumber, result))
            std::cout << "Parse success:  '" << s << "' -> " << result << "\n";
        else
            std::cout << "Parse rejected: '" << s << "' at '" << std::string(f,l) << "'\n";
    

打印

Parse success:  '1.23' -> 1.23
Parse success:  '.123' -> 0.123
Parse success:  '2.e6' -> 2e+06
Parse success:  'inf' -> inf
Parse rejected: '3.2323' at '3.2323'
Parse rejected: 'nan' at 'nan'

¹这里必须明确地完成分配,因为我们使用语义操作,它们通常会抑制自动属性传播

【讨论】:

@AmiTavory 我没有做比较。但是考虑一下Spirit has the fastest real number parsers around。现在,考虑编写您建议的语法的复杂性(例如like here)。它在那里涉及 6 条类型擦除规则,它甚至没有将实际值拼凑在一起(祝 that 正确!)。此外,这并不灵活,因为您无法决定分隔符必须是否存在,整数部分是否可以不存在等等。 我认为头对头测试太费力了,这里显然不需要。如果你愿意,你可以做一个简单的测试(例如,不实际计算结果)。 如何应用同样的逻辑来检查边界(即qi::_val 小于256)?天真地,我本以为 raw[qi::uint_[qi::_val = qi::_1]][qi::_pass = qi::_val &lt; 256] 会起作用。 我在这里正式提出了同样的问题:***.com/questions/51272067/…

以上是关于约束现有的 Boost.Spirit real_parser(带有策略)的主要内容,如果未能解决你的问题,请参考以下文章

Boost.Spirit 的单元测试

使用 boost::spirit 解析任意精度整数

使用 boost::spirit::x3 解析成向量<boost::string_view>

使用 boost-spirit 解析 ipv4 地址

Boost.Spirit.X3 中的船长

boost::spirit 解析器的编译错误