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

Writing a parser for a matrix-like input with Boost Spirit

本文关键字:输入 Spirit Boost 使用      更新时间:2023-10-16

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

最初的想法是将输入保存在 2d std 向量中,以便进一步处理数据。这是我第一次编写解析器,我对 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> >之类的函数。这可能吗?或者我如何实际实施我最初的想法?

我建议:

  • 不使用语义操作进行属性传播。你可以用它来添加验证标准(见Boost Spirit:"语义行为是邪恶的"?

  • 使用自动属性传播,因此不必传递引用

  • 除非有迫切的理由,否则不会在解析期间进行验证。

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

住在科里鲁

#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<<"nt",", "));
            std::cout << "n]n";
        } else {
            std::cout << "Parse failedn";
        }
    }
    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, 
]

如果您想尽早捕获不一致的行长度,请参阅例如以下答案:

    提升::
  • 精神::QI解析器:解析元素的索引(当你知道前面的维度时)
  • 使用
  • boost::spirit 读取 2D 数组C++(将检查添加为后置条件,并在 UPDATE 下使用语义操作进行早期检查)

您需要将表达式分解为如下所示的内容:

rows: DOUBLE | rows ',' DOUBLE
columns: rows | rows ';' rows
matrix: IDENTIFIER '.' '{' IDENTIFIER '}' '=' '[' columns ']'

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

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