【问题标题】:Using boost::spirit to parse named parameters in any order使用 boost::spirit 以任意顺序解析命名参数
【发布时间】:2016-02-06 22:36:27
【问题描述】:

我正在为一种输入文件编写解析器。输入文件如下所示:

[CalculationBlock]
CalculationTitle="Test Parser Input System" , MatchingRadius=25.0, StepSize=0.01,ProblemType=RelSchroedingerEqn
MaxPartialWaveJ=800, SMatConv=10E-8
PartialWaveConv= 10E-8, SmallValueLimit = 10E-8
PotentialRadType=HeavyIon
[end]

本质上它分为以[BlockName] 开头的块,然后在其中包含一组命名参数。命名参数可以用',''\n'字符分隔。

使用我上面给出的不完整输入文件,我想为其编写一个解析器,作为更完整输入文件的起点。我这样做了,但是解析器有一个我不知道如何解决的弱点。它不是独立于参数顺序的。例如,如果用户将参数 PartialWaveConv= 10E-8 放在 SMatConv=10E-8 之前,它将失败。

我曾短暂考虑过枚举块中每个可能的参数顺序,但我放弃了它,因为有 n 个参数值对的 n! 排列。 所以我的问题是:有没有办法让解析器独立于参数排序?

我写的玩具解析器如下,如果是业余的,我很抱歉,这是我第一次涉足boost,更不用说boost.spirit了。

#include<string>
#include<iostream>
#include<cstdlib>
#include<fstream>
#include<boost/config/warning_disable.hpp>
#include<boost/spirit/include/qi.hpp>
#include<boost/spirit/include/phoenix_core.hpp>
#include<boost/spirit/include/phoenix_operator.hpp>
#include<boost/spirit/include/phoenix_object.hpp>
#include<boost/fusion/include/adapt_struct.hpp>
#include<boost/fusion/include/io.hpp>
#include<boost/spirit/include/support_istream_iterator.hpp>

namespace blocks
{
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phoenix = boost::phoenix;

struct CalcBlock
{
    std::string calculationTitle;
    float matchingRad;
    float stepSize;
    std::string problemType;
    int maxPartialWaveJ;
    float sMatrixConvergenceValue;
    float partialWaveConvergenceValue;
    float smallValueLimit;
    std::string potentialRadType;
};

}

//tell fusion about the block structure
BOOST_FUSION_ADAPT_STRUCT(blocks::CalcBlock,
                        (std::string, calculationTitle)
                        (float, matchingRad)
                        (float, stepSize)
                        (std::string, problemType)
                        (int, maxPartialWaveJ)
                        (float, sMatrixConvergenceValue)
                        (float, partialWaveConvergenceValue)
                        (float, smallValueLimit)
                        (std::string, potentialRadType)
)

namespace blocks
{

template <typename Iterator>
struct CalcBlockParser : qi::grammar<Iterator, CalcBlock(), boost::spirit::ascii::blank_type>
{
    CalcBlockParser() : CalcBlockParser::base_type(start)
    {
        using qi::int_;
        using qi::lit;
        using qi::float_;
        using qi::lexeme;
        using ascii::char_;

        quotedString %= lexeme['"' >> +(char_ - '"' - '\n') >> '"'];
        plainString %= lexeme[ +(char_ - ' ' - ',' - '\n') ];

        start %=
            lit("[CalculationBlock]") >> '\n'
            >> lit("CalculationTitle") >> '=' >> quotedString >> (lit(',') | lit('\n'))
            >> lit("MatchingRadius") >> '=' >> float_ >> (lit(',') | lit('\n'))
            >> lit("StepSize") >> '=' >> float_ >> (lit(',') | lit('\n'))
            >> lit("ProblemType") >> '=' >> plainString >> (lit(',') | lit('\n'))
            >> lit("MaxPartialWaveJ") >> '=' >> int_ >> (lit(',') | lit('\n'))
            >> lit("SMatConv") >> '=' >> float_ >> (lit(',') | lit('\n'))
            >> lit("PartialWaveConv") >> '=' >> float_ >> (lit(',') | lit('\n'))
            >> lit("SmallValueLimit") >> '=' >> float_ >> (lit(',') | lit('\n'))
            >> lit("PotentialRadType") >> '=' >> plainString
            >> lit("\n[end]\n");
    }

    qi::rule<Iterator, std::string(), boost::spirit::ascii::blank_type> quotedString;
    qi::rule<Iterator, std::string(), boost::spirit::ascii::blank_type> plainString;
    qi::rule<Iterator, CalcBlock(), boost::spirit::ascii::blank_type> start;
};

}

using std::cout;
using std::endl;
namespace spirit = boost::spirit;
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "\nUsage:\n\t./echos InputFileName\n" << endl;
        return EXIT_FAILURE;
    }

    std::string inputFileName(argv[1]);
    cout << "Reading input from the file: " << inputFileName << endl;
    std::ifstream input(inputFileName);
    input.unsetf(std::ios::skipws);

    spirit::istream_iterator start(input);
    spirit::istream_iterator stop;

    typedef blocks::CalcBlockParser<spirit::istream_iterator> CalcBlockParser;

    CalcBlockParser cbParser;

    blocks::CalcBlock cb;

    bool success = phrase_parse(start, stop, cbParser, boost::spirit::ascii::blank, cb);

    if (success && start == stop)
    {
        std::cout << boost::fusion::tuple_open('[');
        std::cout << boost::fusion::tuple_close(']');
        std::cout << boost::fusion::tuple_delimiter(", ");

        std::cout << "-------------------------\n";
        std::cout << "Parsing succeeded\n";
        std::cout << "got: " << boost::fusion::as_vector(cb) << std::endl;
        std::cout << "\n-------------------------\n";
    }
    else
    {
        std::cout << boost::fusion::tuple_open('[');
        std::cout << boost::fusion::tuple_close(']');
        std::cout << boost::fusion::tuple_delimiter(", ");

        std::cout << "-------------------------\n";
        std::cout << "Parsing failed\n";
        std::cout << "got: " << boost::fusion::as_vector(cb) << std::endl;
        std::cout << "\n-------------------------\n";
    }

    return EXIT_SUCCESS;
}

【问题讨论】:

  • 你考虑过 boost::program_options 吗?
  • 我没有,我将不得不看看它。 boost::program_options 从长远来看可能会更简单,但现在,我想弄清楚这一点,既因为它给了我问题,我想知道解决方案,而且因为如果我有学习精神会很方便更棘手的事情。不过还是谢谢你的建议。
  • @ravenspoint 我看不出这将如何应用在这里。我几乎可以看到 Boost Property Tree 将如何成为一种选择。但我认为两者都意味着改变输入格式

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


【解决方案1】:

为了好玩/完整,我复习了语法并提出了以下测试。

我左右提出了一些改进建议(就像OP在直播中看到的那样),结果代码、测试和输出都在这里:

Live On Coliru

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <fstream>
#include <iostream>

namespace blocks {
    struct CalcBlock {
        std::string calculationTitle;
        float       matchingRad;
        float       stepSize;
        std::string problemType;
        int         maxPartialWaveJ;
        float       sMatrixConvergenceValue;
        float       partialWaveConvergenceValue;    
        float       smallValueLimit;
        std::string potentialRadType;
    };
}

BOOST_FUSION_ADAPT_STRUCT(blocks::CalcBlock, // Boost 1.58+ style adapt-struct
        calculationTitle, matchingRad, stepSize, problemType, maxPartialWaveJ,
        sMatrixConvergenceValue, partialWaveConvergenceValue, smallValueLimit,
        potentialRadType)

namespace blocks {

    namespace qi = boost::spirit::qi;

    template <typename Iterator>
    struct CalcBlockParser : qi::grammar<Iterator, CalcBlock()> {

        CalcBlockParser() : CalcBlockParser::base_type(start) {

            using namespace qi;
            auto eol_ = copy((',' >> *eol) | +eol); // http://stackoverflow.com/a/26411266/85371 (!)

            quotedString = '"' >> +~char_("\"\n") >> '"';
            plainString  =  +~char_(" ,\n");

            start        = skip(blank) [cbRule];

            cbRule       = lexeme["[CalculationBlock]"] >> eol 
              >> (
                      (lexeme["CalculationTitle"] >> '=' >> quotedString >> eol_)
                    ^ (lexeme["MatchingRadius"]   >> '=' >> float_       >> eol_)
                    ^ (lexeme["StepSize"]         >> '=' >> float_       >> eol_)
                    ^ (lexeme["ProblemType"]      >> '=' >> plainString  >> eol_)
                    ^ (lexeme["MaxPartialWaveJ"]  >> '=' >> int_         >> eol_)
                    ^ (lexeme["SMatConv"]         >> '=' >> float_       >> eol_)
                    ^ (lexeme["PartialWaveConv"]  >> '=' >> float_       >> eol_)
                    ^ (lexeme["SmallValueLimit"]  >> '=' >> float_       >> eol_)
                    ^ (lexeme["PotentialRadType"] >> '=' >> plainString  >> eol_)
                 )
             >> lexeme["[end]"]
             >> *eol 
             >> eoi;
        }

      private:
        qi::rule<Iterator, CalcBlock()> start;
        qi::rule<Iterator, CalcBlock(), qi::blank_type> cbRule;
        // lexemes:
        qi::rule<Iterator, std::string()> quotedString, plainString;
    };
}

using   boost::fusion::as_vector;
typedef boost::spirit::istream_iterator It;

int main(int argc, char **argv) {
    if (argc != 2) {
        std::cout << "Usage:\n\t" << argv[0] << " InputFileName" << std::endl;
        return 1;
    }

    std::string inputFileName(argv[1]);
    std::cout << "Reading input from the file: " << inputFileName << std::endl;
    std::ifstream input(inputFileName);
    input.unsetf(std::ios::skipws);

    It start(input), stop;

    blocks::CalcBlock cb;
    blocks::CalcBlockParser<It> cbParser;

    bool success = parse(start, stop, cbParser, cb);

    {
        using namespace boost::fusion;
        std::cout << tuple_open('[') << tuple_close(']') << tuple_delimiter(", ");
    }

    std::cout << "-------------------------\n";
    std::cout << "Parsing " << (success?"succeeded":"failed") << "\n";
    std::cout << "got: "    << as_vector(cb)                  << "\n";
    std::cout << "-------------------------\n";
}

输入:

[CalculationBlock]
CalculationTitle="Test Parser Input System"


SMatConv=10E-8,


PartialWaveConv= 10E-8, MaxPartialWaveJ=800, SmallValueLimit = 10E-8

PotentialRadType=HeavyIon , MatchingRadius=25.0, StepSize=0.01,ProblemType=RelSchroedingerEqn

[end]

输出:

Reading input from the file: input.txt
-------------------------
Parsing succeeded
got: [Test Parser Input System, 25, 0.01, RelSchroedingerEqn, 800, 1e-07, 1e-07, 1e-07, HeavyIon]
-------------------------

【讨论】:

【解决方案2】:

必须使用置换运算符^:

start %=
        lit("[CalculationBlock]") >> '\n' >>
        (
        (lit("CalculationTitle") >> '=' >> quotedString >> (lit(',') | lit)('\n')))
        ^ (lit("MatchingRadius") >> '=' >> float_ >> (lit(',') | lit('\n')))
        ^ (lit("StepSize") >> '=' >> float_ >> (lit(',') | lit('\n')))
        ^ (lit("ProblemType") >> '=' >> plainString >> (lit(',') | lit('\n')))
        ^ (lit("MaxPartialWaveJ") >> '=' >> int_ >> (lit(',') | lit('\n')))
        ^ (lit("SMatConv") >> '=' >> float_ >> (lit(',') | lit('\n')))
        ^ (lit("PartialWaveConv") >> '=' >> float_ >> (lit(',') | lit('\n')))
        ^ (lit("SmallValueLimit") >> '=' >> float_ >> (lit(',') | lit('\n')))
        ^ (lit("PotentialRadType") >> '=' >> plainString >> (lit(',') | lit('\n')))
        )
        >> lit("\n[end]\n");

【讨论】:

  • 我真希望我在发布问题之前就发现了那个操作员。也就是说,这样做会混淆信息排序到我通过融合访问的结构吗?
  • 稍后的简短测试表明它确实会干扰结构中信息的排序。事实上,如果我更改顺序,解析会失败,有没有办法解决这个问题?
  • @JamesMatta 您的简短测试是错误的。此外,它只是记录在案:boost.org/doc/libs/1_59_0/libs/spirit/doc/html/spirit/qi/…
  • @sehe 即使有文档,精神对于新手来说也很难,它本质上是使用运算符重载和模板元编程在语言中定义一种语言,后者是不平凡的。无论如何,我发现 Jepessen 的答案出现后的文档,并没有很好地解释 ^ 和融合元组系统之间的交互。我将不得不测试并了解更多信息。
  • @JamesMatta 它没有解释交互,因为没有。您的期望应该与他们写完全相同的here 时完全相同。您是否意识到(可以理解的)混乱的根源?人们太聪明了,他们在没有意义的地方寻找意义。
猜你喜欢
  • 1970-01-01
  • 2021-01-27
  • 2010-12-24
  • 2017-10-30
  • 1970-01-01
  • 2011-03-05
  • 2019-07-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多