【问题标题】:Spirit X3: parser with internal stateSpirit X3:具有内部状态的解析器
【发布时间】:2018-06-12 16:31:37
【问题描述】:

我想高效地解析类似 CSV 的大型文件,这些文件的列顺序是我在运行时获得的。使用 Spirit Qi,我将使用 lazy 辅助解析器解析每个字段,该解析器将在运行时选择将哪个特定于列的解析器应用于每个列。但是X3好像没有lazy(虽然是listed in documentation)。在阅读了关于 SO 的建议后,我决定编写一个自定义解析器。

结果非常好,但现在我注意到我真的不需要在自定义解析器本身之外的任何地方公开pos 变量。我尝试将其放入自定义解析器本身并开始收到编译器错误,指出column_value_parser 对象是只读的。我可以以某种方式将pos 放入解析器结构吗?

得到编译时错误的简化代码,并注释掉了我的工作版本的部分内容:

#include <iostream>
#include <variant>

#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/support.hpp>

namespace helpers {
    // https://bitbashing.io/std-visit.html
    template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
    template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
}

auto const unquoted_text_field = *(boost::spirit::x3::char_ - ',' - boost::spirit::x3::eol);

struct text { };
struct integer { };
struct real { };
struct skip { };
typedef std::variant<text, integer, real, skip> column_variant;

struct column_value_parser : boost::spirit::x3::parser<column_value_parser> {
    typedef boost::spirit::unused_type attribute_type;

    std::vector<column_variant>& columns;
    // size_t& pos;
    size_t pos;

    // column_value_parser(std::vector<column_variant>& columns, size_t& pos)
    column_value_parser(std::vector<column_variant>& columns)
        : columns(columns)
    //    , pos(pos)
        , pos(0)
    { }

    template<typename It, typename Ctx, typename Other, typename Attr>
    bool parse(It& f, It l, Ctx& ctx, Other const& other, Attr& attr) const {
        auto const saved_f = f;
        bool successful = false;

        visit(
            helpers::overloaded {
                [&](skip const&) {
                    successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::omit[unquoted_text_field]);
                },
                [&](text& c) {
                    std::string value;
                    successful = boost::spirit::x3::parse(f, l, unquoted_text_field, value);
                    if(successful) {
                        std::cout << "Text: " << value << '\n';
                    }
                },
                [&](integer& c) {
                    int value;
                    successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::int_, value);
                    if(successful) {
                        std::cout << "Integer: " << value << '\n';
                    }
                },
                [&](real& c) {
                    double value;
                    successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::double_, value);
                    if(successful) {
                        std::cout << "Real: " << value << '\n';
                    }
                }
            },
            columns[pos]);

        if(successful) {
            pos = (pos + 1) % columns.size();
            return true;
        } else {
            f = saved_f;
            return false;
        }
    }
};


int main(int argc, char *argv[])
{
    std::string input = "Hello,1,13.7,XXX\nWorld,2,1e3,YYY";

    // Comes from external source.
    std::vector<column_variant> columns = {text{}, integer{}, real{}, skip{}};
    size_t pos = 0;

    boost::spirit::x3::parse(
        input.begin(), input.end(),
//         (column_value_parser(columns, pos) % ',') % boost::spirit::x3::eol);
        (column_value_parser(columns) % ',') % boost::spirit::x3::eol);
}

XY:我的目标是在合理的时间内在 RAM 很少的机器上解析 ~500 GB 的伪 CSV 文件,转换为(大致)[行号、列名、值]的列表,然后入库。格式实际上比 CSV 复杂一点:数据库转储以……人性化的方式格式化,列值实际上是几个小的子语言(例如日期,或者,呃,类似于填充到单个字段中的整个 apache 日志行),我经常只提取每列的一个特定部分。不同的文件可能有不同的列和不同的顺序,我只能通过解析另一组包含原始查询的文件来学习。值得庆幸的是,Spirit 让它变得轻而易举……

【问题讨论】:

  • 房间里的大象:为什么不导出 XML 或使用数据库而不是转储?
  • @sehe,哦,我希望它以一种更理智的方式完成……作为一个业余爱好项目,我只是说服该数据集的所有者给我他们可以轻松手动检查的内容,只需很少的技术技能.它仍然比他们自己使用的 XLS 好。
  • 我会首先解析自定义 AST 节点(例如在共享/映射内存容器中)并从那里实现所有逻辑。

标签: c++ c++17 boost-spirit boost-spirit-x3


【解决方案1】:

三个答案:

  1. 最简单的解决方法是将pos 设为mutable 成员
  2. X3 的核心答案是x3::with&lt;&gt;
  3. 功能组合

1。使pos 可变

Live On Wandbox

#include <iostream>
#include <variant>

#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/support.hpp>

namespace helpers {
    // https://bitbashing.io/std-visit.html
    template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
    template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
}

auto const unquoted_text_field = *(boost::spirit::x3::char_ - ',' - boost::spirit::x3::eol);

struct text { };
struct integer { };
struct real { };
struct skip { };
typedef std::variant<text, integer, real, skip> column_variant;

struct column_value_parser : boost::spirit::x3::parser<column_value_parser> {
    typedef boost::spirit::unused_type attribute_type;

    std::vector<column_variant>& columns;
    size_t mutable pos = 0;
    struct pos_tag;

    column_value_parser(std::vector<column_variant>& columns)
        : columns(columns)
    { }

    template<typename It, typename Ctx, typename Other, typename Attr>
    bool parse(It& f, It l, Ctx& /*ctx*/, Other const& /*other*/, Attr& /*attr*/) const {
        auto const saved_f = f;
        bool successful = false;

        visit(
            helpers::overloaded {
                [&](skip const&) {
                    successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::omit[unquoted_text_field]);
                },
                [&](text&) {
                    std::string value;
                    successful = boost::spirit::x3::parse(f, l, unquoted_text_field, value);
                    if(successful) {
                        std::cout << "Text: " << value << '\n';
                    }
                },
                [&](integer&) {
                    int value;
                    successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::int_, value);
                    if(successful) {
                        std::cout << "Integer: " << value << '\n';
                    }
                },
                [&](real&) {
                    double value;
                    successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::double_, value);
                    if(successful) {
                        std::cout << "Real: " << value << '\n';
                    }
                }
            },
            columns[pos]);

        if(successful) {
            pos = (pos + 1) % columns.size();
            return true;
        } else {
            f = saved_f;
            return false;
        }
    }
};


int main() {
    std::string input = "Hello,1,13.7,XXX\nWorld,2,1e3,YYY";

    std::vector<column_variant> columns = {text{}, integer{}, real{}, skip{}};

    boost::spirit::x3::parse(
        input.begin(), input.end(),
        (column_value_parser(columns) % ',') % boost::spirit::x3::eol);
}

2。 x3::with&lt;&gt;

这很相似,但具有更好的(重新)入口和封装:

Live On Wandbox

#include <iostream>
#include <variant>

#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/support.hpp>

namespace helpers {
    // https://bitbashing.io/std-visit.html
    template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
    template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
}

auto const unquoted_text_field = *(boost::spirit::x3::char_ - ',' - boost::spirit::x3::eol);

struct text { };
struct integer { };
struct real { };
struct skip { };
typedef std::variant<text, integer, real, skip> column_variant;

struct column_value_parser : boost::spirit::x3::parser<column_value_parser> {
    typedef boost::spirit::unused_type attribute_type;

    std::vector<column_variant>& columns;

    column_value_parser(std::vector<column_variant>& columns)
        : columns(columns)
    { }

    template<typename It, typename Ctx, typename Other, typename Attr>
    bool parse(It& f, It l, Ctx const& ctx, Other const& /*other*/, Attr& /*attr*/) const {
        auto const saved_f = f;
        bool successful = false;

        size_t& pos = boost::spirit::x3::get<pos_tag>(ctx).value;

        visit(
            helpers::overloaded {
                [&](skip const&) {
                    successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::omit[unquoted_text_field]);
                },
                [&](text&) {
                    std::string value;
                    successful = boost::spirit::x3::parse(f, l, unquoted_text_field, value);
                    if(successful) {
                        std::cout << "Text: " << value << '\n';
                    }
                },
                [&](integer&) {
                    int value;
                    successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::int_, value);
                    if(successful) {
                        std::cout << "Integer: " << value << '\n';
                    }
                },
                [&](real&) {
                    double value;
                    successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::double_, value);
                    if(successful) {
                        std::cout << "Real: " << value << '\n';
                    }
                }
            },
            columns[pos]);

        if(successful) {
            pos = (pos + 1) % columns.size();
            return true;
        } else {
            f = saved_f;
            return false;
        }
    }

    template <typename T>
    struct Mutable { T mutable value; };
    struct pos_tag;

    auto invoke() const {
        return boost::spirit::x3::with<pos_tag>(Mutable<size_t>{}) [ *this ];
    }
};


int main() {
    std::string input = "Hello,1,13.7,XXX\nWorld,2,1e3,YYY";

    std::vector<column_variant> columns = {text{}, integer{}, real{}, skip{}};
    column_value_parser p(columns);

    boost::spirit::x3::parse(
        input.begin(), input.end(),
        (p.invoke() % ',') % boost::spirit::x3::eol);
}

3。功能组成

因为在 X3 中要容易得多,所以我最喜欢的是按需生成解析器。

没有要求,这是我建议的最简单的:

Live On Wandbox

#include <boost/spirit/home/x3.hpp>
namespace x3 = boost::spirit::x3;

namespace CSV {
    struct text    { };
    struct integer { };
    struct real    { };
    struct skip    { };

    auto const unquoted_text_field = *~x3::char_(",\n");
    static inline auto as_parser(skip)    { return x3::omit[unquoted_text_field]; }
    static inline auto as_parser(text)    { return unquoted_text_field;           }
    static inline auto as_parser(integer) { return x3::int_;                      }
    static inline auto as_parser(real)    { return x3::double_;                   }

    template <typename... Spec>
    static inline auto line_parser(Spec... spec) {
        auto delim = ',' | &(x3::eoi | x3::eol);
        return ((as_parser(spec) >> delim) >> ... >> x3::eps);
    }

    template <typename... Spec> static inline auto csv_parser(Spec... spec) {
        return line_parser(spec...) % x3::eol;
    }
}

#include <iostream>
#include <iomanip>
using namespace CSV;

int main() {
    std::string const input = "Hello,1,13.7,XXX\nWorld,2,1e3,YYY";
    auto f = begin(input), l = end(input);

    auto p = csv_parser(text{}, integer{}, real{}, skip{});

    if (parse(f, l, p)) {
        std::cout << "Parsed\n";
    } else {
        std::cout << "Failed\n";
    }

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

启用调试信息的版本:

Live On Wandbox

<line>
  <try>Hello,1,13.7,XXX\nWor</try>
  <CSV::text>
    <try>Hello,1,13.7,XXX\nWor</try>
    <success>,1,13.7,XXX\nWorld,2,</success>
  </CSV::text>
  <CSV::integer>
    <try>1,13.7,XXX\nWorld,2,1</try>
    <success>,13.7,XXX\nWorld,2,1e</success>
  </CSV::integer>
  <CSV::real>
    <try>13.7,XXX\nWorld,2,1e3</try>
    <success>,XXX\nWorld,2,1e3,YYY</success>
  </CSV::real>
  <CSV::skip>
    <try>XXX\nWorld,2,1e3,YYY</try>
    <success>\nWorld,2,1e3,YYY</success>
  </CSV::skip>
  <success>\nWorld,2,1e3,YYY</success>
</line>
<line>
  <try>World,2,1e3,YYY</try>
  <CSV::text>
    <try>World,2,1e3,YYY</try>
    <success>,2,1e3,YYY</success>
  </CSV::text>
  <CSV::integer>
    <try>2,1e3,YYY</try>
    <success>,1e3,YYY</success>
  </CSV::integer>
  <CSV::real>
    <try>1e3,YYY</try>
    <success>,YYY</success>
  </CSV::real>
  <CSV::skip>
    <try>YYY</try>
    <success></success>
  </CSV::skip>
  <success></success>
</line>
Parsed

注意事项、注意事项:

  • 对于任何mutable,请注意副作用。例如。如果您有a | b 并且a 包含column_value_parser,则当a 失败并且匹配b 时,递增pos 的副作用将不会回滚。

    简而言之,这会使您的解析函数不纯。

【讨论】:

  • 我正在考虑让它可变,但我认为 x3 构造解析器是有充分理由的。如果说没关系,那就太好了!这是一个爱好项目,所以硬核方法甚至更好(-:我会等待第三个选择,虽然我想知道 - 我希望按需构建解析器不会让它变慢?
  • 你看到警告了吗?是的,x3 组合是有充分理由的。主要是状态和静态逻辑的分离,意味着编译器对优化有很好的处理。按需生成是否会使其变慢完全取决于生成时间和重复使用频率。
  • 出于好奇,所以我以后制作的任何样本都可以具有现实生活的适用性,目标是什么?您现在只是打印并“丢弃”属性。我假设有一个目标数据结构。
  • 没有要求,这是我建议的最简单的方法:wandbox.org/permlink/jXKEAskTctV2iDVT - 在没有已知目标的情况下,我将避免解析为适应的结构或解析为基于真正的通用容器动态 CSV 规范。
  • 更新了答案。另见例如X3Qi 中的这个较旧的 CSV 示例。上下文是this discussion。注意performance figures there are probably skewed
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-25
  • 1970-01-01
相关资源
最近更新 更多