使用Boost Spirit为类似矩阵的输入编写解析器

时间:2014-11-20 22:39:13

标签: c++ parsing boost boost-spirit

我试图编写一个能够接受MATRIX.{variableName} = [1,2,3;4,5,6]形式输入的解析器,其中矩阵的表示(在这种情况下为2x3矩阵)有点像MATLAB' s格式(分号表示新行)。

最初的想法是将输入保存在2d标准向量中,以便进一步处理数据。这是我第一次编写解析器,而且我对Spirit框架有点无能为力。

我当前(不那么直观)的解决方案是输入类似MATRIX (2,3) = [1,2,3,4,5,6]来表示与上面相同的矩阵并将数据​​保存在一维向量中并利用行和列数据稍后进行处理(我相信有点像Eigen对动态矩阵的实现)。

namespace client
{
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    namespace phoenix = boost::phoenix;
    namespace fusion = boost::fusion;

    template <typename Iterator>
    bool parse_matrix(Iterator first, Iterator last, unsigned& rows, unsigned& cols, std::vector<double>& vals)
    {
        using qi::double_;
        using qi::uint_;
        using qi::_1;
        using qi::lit;
        using qi::phrase_parse;
        using ascii::space;
        using phoenix::push_back;

        double rN = 0.0;
        double iN = 0.0;
        unsigned i=0;
        rows = 0, cols = 0;
        bool r = phrase_parse(first, last,

            //  Begin grammar
            (
                lit("MATRIX") >> '(' >> uint_[phoenix::ref(rows) = _1] >> ',' >> uint_[phoenix::ref(cols) = _1] >> ')' >> '='
                 >> '[' >> double_[push_back(phoenix::ref(vals),_1)]
                        >> *(',' >> double_[push_back(phoenix::ref(vals),_1)]) >> ']'
                // |   double_[ref(rN) = _1]
            ),
            //  End grammar

            space);

        if (!r || first != last) // fail if we did not get a full match
            return false;
        if (vals.size() != (rows*cols)) 
            return false;
        // c = std::complex<double>(rN, iN);
        return r;
    }
}

我想也许有可能在解析某些字符(如分号)时调用std::vector<double>这样的函数。std::vector<std::vector<double> >。这可能吗?或者我如何实际实现我最初的想法?

2 个答案:

答案 0 :(得分:1)

我建议:

  • 不使用语义动作进行属性传播。您可以使用它来添加验证条件(请参阅 Boost Spirit: "Semantic actions are evil"?

  • 使用自动属性传播,因此您不必传递参考

  • 在解析过程中不进行验证,除非您原因这样做。

最小可行解析器然后变为:

<强> Live On Coliru

#include <boost/spirit/include/qi.hpp>

using It  = boost::spirit::istream_iterator;
using Row = std::vector<double>;
using Mat = std::vector<Row>;

int main() {
    It f(std::cin>>std::noskipws), l;

    Mat matrix;
    std::string name;

    {
        using namespace boost::spirit::qi;
        rule<It, std::string()> varname_ = char_("a-zA-Z_") >> *char_("a-zA-Z0-9_");

        if (phrase_parse(f, l, 
                lit("MATRIX") >> '.' >> '{' >> varname_ >> '}' >> '=' 
                    >> '[' >> (int_ % ',' % ';') >> ']',
                space, name, matrix))
        {
            std::cout << "Parsed: variabled named '" << name << "' [";

            for(auto& row : matrix)
                std::copy(row.begin(), row.end(), std::ostream_iterator<double>(std::cout<<"\n\t",", "));
            std::cout << "\n]\n";
        } else {
            std::cout << "Parse failed\n";
        }
    }

    if (f!=l)
        std::cout << "Remaining input: '" << std::string(f,l) << "'\n";
}

可以看到为输入"MATRIX.{variable_name}=[1,2,3;4,5,6]"打印以下输出:

Parsed: variabled named 'variable_name' [
    1, 2, 3, 
    4, 5, 6, 
]

如果您希望尽早捕获不一致的行长度,请参阅例如:这个答案:

答案 1 :(得分:0)

你需要将表达式分解为:

rows: DOUBLE | rows ',' DOUBLE

columns: rows | rows ';' rows

matrix: IDENTIFIER '.' '{' IDENTIFIER '}' '=' '[' columns ']'

现在,您的行可以有vector<double>。列的vector< vector<double> >,并将结果保存在矩阵中。

当然,当您在矩阵中获得列时,应检查所有行是否具有相同的大小。它不是必需的,但显然矩阵(例如[1,2;3,4,5])无效,即使语法允许它。