处理 INI 文件中重复的部分名称

时间:2021-04-07 18:02:58

标签: boost ini

我需要从 INI 文件加载这些值并使用 C++ Boost 库在应用程序中打印它们。这些部分具有重复的名称。我被限制只能使用 C++ Boost 库。

numColors = 4
boardSize = 11
numSnails = 2
[initialization]
id = 0
row = 3
col = 4
orientation = 0
[initialization]
id = 1
row = 5
col = 0
orientation = 1
[color]
id = 0
nextColor = 1
deltaOrientation = +2
[color]
id = 1   
nextColor = 2
deltaOrientation = +1
[color]
id = 2
nextColor = 3
deltaOrientation = -2
[color]
id = 3
nextColor = 0
deltaOrientation = -1

2 个答案:

答案 0 :(得分:2)

我认为对于一个随机的路人来说,the Boost Spirit answer 可能看起来很复杂/矫枉过正。

我想再试一次,因为现在是 2021 年,无论是使用 C++17,我们都可以仅使用标准库来完成合理的工作。

事实证明,还有很多工作要做。 Qi 实现需要 86 行代码,但标准库实现需要 136 行。此外,调试/编写花了我更长的时间(几个小时)。特别是很难将 '=', '[', ']' 作为带有 std::istream& 的标记边界。我使用了这个答案中的 ctype 方面方法:How do I iterate over cin line by line in C++?

<块引用>

我确实在 DebugPeeker(20 行)中留了下来,所以您或许可以自己理解。

短期博览会

顶级解析函数看起来很正常,并显示了我想要实现的目标:自然的 std::istream 提取:

static Ast::File std_parse_game(std::string_view input) {
    std::istringstream iss{std::string(input)};

    using namespace Helpers;
    if (Ast::File parsed; iss >> parsed)
        return parsed;
    throw std::runtime_error("Unable to parse game");
}

其余的都在命名空间 Helpers 中:

static inline std::istream& operator>>(std::istream& is, Ast::File& v) {

    for (section s; is >> s;) {
        if (s.name == "parameters")
            is >> v.parameters;
        else if (s.name == "initialization")
            is >> v.initializations.emplace_back();
        else if (s.name == "color")
            is >> v.colors.emplace_back();
        else
            is.setstate(std::ios::failbit);
    }
    if (is.eof())
        is.clear();
    return is;
}

到目前为止,这是一个很好的回报。不同的部分类型是相似的:

static inline std::istream& operator>>(std::istream& is, Ast::Parameters& v) {
    return is
        >> entry{"numColors", v.numColors}
        >> entry{"boardSize", v.boardSize}
        >> entry{"numSnails", v.numSnails};
}

static inline std::istream& operator>>(std::istream& is, Ast::Initialization& v) {
    return is
        >> entry{"id", v.id}
        >> entry{"row", v.row}
        >> entry{"col", v.col}
        >> entry{"orientation", v.orientation};
}

static inline std::istream& operator>>(std::istream& is, Ast::Color& v) {
    return is
        >> entry{"id", v.id}
        >> entry{"nextColor", v.nextColor}
        >> entry{"deltaOrientation", v.deltaOrientation};
}

现在,如果一切都像这样一帆风顺,我不会推荐精神。现在我们进入条件解析。 entry{"name", value} 公式使用“操纵器类型”:

template <typename T> struct entry {
    entry(std::string name, T& into) : _name(name), _into(into) {}
    std::string _name;
    T& _into;
    friend std::istream& operator>>(std::istream& is, entry e) {
        return is >> expect{e._name} >> expect{'='} >> e._into;
    }
};

同样,部分使用 expecttoken

struct section {
    std::string name;
    friend std::istream& operator>>(std::istream& is, section& s) {
        if (is >> expect('['))
            return is >> token{s.name} >> expect{']'};
        return is;
    }
};

条件对于能够在不将流置于硬失败模式的情况下检测到 EOF 很重要 (is.bad() != is.fail())。

expect 建立在 token 之上:

template <typename T> struct expect {
    expect(T expected) : _expected(expected) {}
    T _expected;
    friend std::istream& operator>>(std::istream& is, expect const& e) {
        if (T actual; is >> token{actual})
            if (actual != e._expected)
                is.setstate(std::ios::failbit);
        return is;
    }
};

您会注意到错误信息少得多。我们只是让 流 fail(),以防未找到预期的令牌。

这就是真正复杂的地方。我不想解析字符 特点。但是使用 std::string 读取 operator>> 只会停止 空格,意味着节名称会“吃掉”括号:parameters] 而不是 parameters,如果没有,键可能会吃掉 = 字符 分隔空间。

在上面链接的答案中,我们学习了如何建立自己的角色 分类区域设置方面:

// make sure =,[,] break tokens
struct mytoken_ctype : std::ctype<char> {
    static auto const* get_table() {
        static std::vector rc(table_size, std::ctype_base::mask());

        rc[' '] = rc['\f'] = rc['\v'] = rc['\t'] = rc['\r'] = rc['\n'] =
            std::ctype_base::space;
        // crucial for us:
        rc['='] = rc['['] = rc[']'] = std::ctype_base::space;
        return rc.data();
    }

    mytoken_ctype() : std::ctype<char>(get_table()) {}
};

然后我们需要使用它,但前提是我们解析 std::string 标记。那 方式,如果我们 expect('=') 它不会跳过 '=' 因为我们的方面称它为空白...

template <typename T> struct token {
    token(T& into) : _into(into) {}
    T& _into;
    friend std::istream& operator>>(std::istream& is, token const& t) {
        std::locale loc = is.getloc();
        if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
            loc = is.imbue(std::locale(std::locale(), new mytoken_ctype()));
        }

        try { is >> t._into; is.imbue(loc); }
        catch (...) { is.imbue(loc); throw; }
        return is;
    }
};

我尽量保持简洁。如果我使用了正确的格式,我们会 还有更多的代码行:)

演示和测试

我使用了相同的 Ast 类型,因此测试两种实现和 比较结果是否相等。

<块引用>

注意

  1. 在 Compiler Explorer 中,我们可以享受 libfmt 以方便输出
  2. 为了进行比较,我使用了一个 C++20 特性来生成编译器operator==

Live On Compiler Explorer

#include <boost/spirit/home/qi.hpp>
#include <boost/fusion/include/io.hpp>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <fmt/ranges.h>
#include <fmt/ostream.h>
namespace qi = boost::spirit::qi;

namespace Ast {
    using Id          = unsigned;
    using Size        = uint16_t; // avoiding char types for easy debug/output
    using Coord       = Size;
    using ColorNumber = Size;
    using Orientation = Size;
    using Delta       = signed;

    struct Parameters {
        Size numColors{}, boardSize{}, numSnails{};

        bool operator==(Parameters const&) const = default;
    };

    struct Initialization {
        Id          id;
        Coord       row;
        Coord       col;
        Orientation orientation;

        bool operator==(Initialization const&) const = default;
    };

    struct Color {
        Id          id;
        ColorNumber nextColor;
        Delta       deltaOrientation;

        bool operator==(Color const&) const = default;
    };

    struct File {
        Parameters                  parameters;
        std::vector<Initialization> initializations;
        std::vector<Color>          colors;

        bool operator==(File const&) const = default;
    };

    using boost::fusion::operator<<;

    template <typename T>
    static inline std::ostream& operator<<(std::ostream& os, std::vector<T> const& v) {
        return os << fmt::format("vector<{}>{}",
                                 boost::core::demangle(typeid(T).name()), v);
    }
}  // namespace Ast

BOOST_FUSION_ADAPT_STRUCT(Ast::Parameters, numColors, boardSize, numSnails)
BOOST_FUSION_ADAPT_STRUCT(Ast::Initialization, id, row, col, orientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::Color, id, nextColor, deltaOrientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::File, parameters, initializations, colors)

template <typename It>
struct GameParser : qi::grammar<It, Ast::File()> {
    GameParser() : GameParser::base_type(start) {
        using namespace qi;
        start = skip(blank)[file];

        auto section = [](const std::string& name) {
            return copy('[' >> lexeme[lit(name)] >> ']' >> (+eol | eoi));
        };
        auto required = [](const std::string& name, auto value) {
            return copy(lexeme[eps > lit(name)] > '=' > value >
                        (+eol | eoi));
        };

        file = parameters >
            *initialization >
            *color >
            eoi; // must reach end of input

        parameters = section("parameters") >
            required("numColors", _size) >
            required("boardSize", _size) >
            required("numSnails", _size);

        initialization = section("initialization") >
            required("id", _id) >
            required("row", _coord) >
            required("col", _coord) >
            required("orientation", _orientation);
            
        color = section("color") >
            required("id", _id) >
            required("nextColor", _colorNumber) >
            required("deltaOrientation", _delta);

        BOOST_SPIRIT_DEBUG_NODES((file)(parameters)(initialization)(color))
    }

  private:
    using Skipper = qi::blank_type;
    qi::rule<It, Ast::File()>                    start;
    qi::rule<It, Ast::File(), Skipper>           file;
    // sections
    qi::rule<It, Ast::Parameters(), Skipper>     parameters;
    qi::rule<It, Ast::Initialization(), Skipper> initialization;
    qi::rule<It, Ast::Color(), Skipper>          color;

    // value types
    qi::uint_parser<Ast::Id>          _id;
    qi::uint_parser<Ast::Size>        _size;
    qi::uint_parser<Ast::Coord>       _coord;
    qi::uint_parser<Ast::ColorNumber> _colorNumber;
    qi::uint_parser<Ast::Orientation> _orientation;
    qi::int_parser<Ast::Delta>        _delta;
};

static Ast::File qi_parse_game(std::string_view input) {
    using SVI = std::string_view::const_iterator;
    static const GameParser<SVI> parser{};

    try {
        Ast::File parsed;
        if (qi::parse(input.begin(), input.end(), parser, parsed)) {
            return parsed;
        }
        throw std::runtime_error("Unable to parse game");
    } catch (qi::expectation_failure<SVI> const& ef) {
        std::ostringstream oss;

        auto where  = ef.first - input.begin();
        auto sol    = 1 + input.find_last_of("\r\n", where);
        auto lineno = 1 + std::count(input.begin(), input.begin() + sol, '\n');
        auto col    = 1 + where - sol;
        auto llen   = input.substr(sol).find_first_of("\r\n");

        oss << "input.txt:" << lineno << ":" << col << " Expected: " << ef.what_ << "\n"
            << " note: " << input.substr(sol, llen) << "\n"
            << " note:"  << std::setw(col) << "" << "^--- here";
        throw std::runtime_error(oss.str());
    }
}

namespace Helpers {
    struct DebugPeeker {
        DebugPeeker(std::istream& is, int line) : is(is), line(line) { dopeek(); }
        ~DebugPeeker() { dopeek(); }

      private:
        std::istream& is;
        int line;

        void dopeek() const {
            std::char_traits<char> t;
            auto ch = is.peek();
            std::cerr << "DEBUG " << line << " Peek: ";
            if (std::isgraph(ch))
                std::cerr << "'" << t.to_char_type(ch) << "'";
            else 
                std::cerr << "<" << ch << ">";
            std::cerr << " " << std::boolalpha << is.good() << "\n";
        }
    };

#define DEBUG_PEEK(is) // Peeker _peek##__LINE__(is, __LINE__);

    // make sure =,[,] break tokens
    struct mytoken_ctype : std::ctype<char> {
        static auto const* get_table() {
            static std::vector rc(table_size, std::ctype_base::mask());

            rc[' '] = rc['\f'] = rc['\v'] = rc['\t'] = rc['\r'] = rc['\n'] =
                std::ctype_base::space;
            // crucial for us:
            rc['='] = rc['['] = rc[']'] = std::ctype_base::space;
            return rc.data();
        }

        mytoken_ctype() : std::ctype<char>(get_table()) {}
    };

    template <typename T> struct token {
        token(T& into) : _into(into) {}
        T& _into;
        friend std::istream& operator>>(std::istream& is, token const& t) {
            DEBUG_PEEK(is);
            std::locale loc = is.getloc();
            if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
                loc = is.imbue(std::locale(std::locale(), new mytoken_ctype()));
            }

            try { is >> t._into; is.imbue(loc); }
            catch (...) { is.imbue(loc); throw; }
            return is;
        }
    };

    template <typename T> struct expect {
        expect(T expected) : _expected(expected) {}
        T _expected;
        friend std::istream& operator>>(std::istream& is, expect const& e) {
            DEBUG_PEEK(is);
            if (T actual; is >> token{actual})
                if (actual != e._expected)
                    is.setstate(std::ios::failbit);
            return is;
        }
    };

    template <typename T> struct entry {
        entry(std::string name, T& into) : _name(name), _into(into) {}
        std::string _name;
        T& _into;
        friend std::istream& operator>>(std::istream& is, entry e) {
            DEBUG_PEEK(is);
            return is >> expect{e._name} >> expect{'='} >> e._into;
        }
    };

    struct section {
        std::string name;
        friend std::istream& operator>>(std::istream& is, section& s) {
            DEBUG_PEEK(is);
            if (is >> expect('['))
                return is >> token{s.name} >> expect{']'};
            return is;
        }
    };

    static inline std::istream& operator>>(std::istream& is, Ast::Parameters& v) {
        DEBUG_PEEK(is);
        return is
            >> entry{"numColors", v.numColors}
            >> entry{"boardSize", v.boardSize}
            >> entry{"numSnails", v.numSnails};
    }

    static inline std::istream& operator>>(std::istream& is, Ast::Initialization& v) {
        DEBUG_PEEK(is);
        return is
            >> entry{"id", v.id}
            >> entry{"row", v.row}
            >> entry{"col", v.col}
            >> entry{"orientation", v.orientation};
    }

    static inline std::istream& operator>>(std::istream& is, Ast::Color& v) {
        DEBUG_PEEK(is);
        return is
            >> entry{"id", v.id}
            >> entry{"nextColor", v.nextColor}
            >> entry{"deltaOrientation", v.deltaOrientation};
    }

    static inline std::istream& operator>>(std::istream& is, Ast::File& v) {
        DEBUG_PEEK(is);

        for (section s; is >> s;) {
            if (s.name == "parameters")
                is >> v.parameters;
            else if (s.name == "initialization")
                is >> v.initializations.emplace_back();
            else if (s.name == "color")
                is >> v.colors.emplace_back();
            else
                is.setstate(std::ios::failbit);
        }
        if (is.eof())
            is.clear();
        return is;
    }
}

static Ast::File std_parse_game(std::string_view input) {
    std::istringstream iss{std::string(input)};

    using namespace Helpers;
    if (Ast::File parsed; iss >> parsed)
        return parsed;
    throw std::runtime_error("Unable to parse game");
}

std::string read_file(const std::string& name) {
    std::ifstream ifs(name);
    return std::string(std::istreambuf_iterator<char>(ifs), {});
}

int main() {
    std::string const game_save = read_file("input.txt");

    Ast::File g1, g2;
    try {
        std::cout << "Qi:    " << (g1 = qi_parse_game(game_save)) << "\n";
    } catch (std::exception const& e) { std::cerr << e.what() << "\n"; }
    try {
        std::cout << "std:   " << (g2 = std_parse_game(game_save)) << "\n";
    } catch (std::exception const& e) { std::cerr << e.what() << "\n"; }

    std::cout << "Equal: " << std::boolalpha << (g1 == g2) << "\n";
}

令我欣慰的是,解析器对数据达成了一致:

Qi:    ((4 11 2)
        vector<Ast::Initialization>{(0 3 4 0), (1 5 0 1)}
        vector<Ast::Color>{(0 1 2), (1 2 1), (2 3 -2), (3 0 -1)})
std:   ((4 11 2)
        vector<Ast::Initialization>{(0 3 4 0), (1 5 0 1)}
        vector<Ast::Color>{(0 1 2), (1 2 1), (2 3 -2), (3 0 -1)})
Equal: true

总结/结论

虽然这个答案是“标准的”和“便携的”,但它有一些缺点。

例如,正确处理当然并不容易,它几乎没有调试选项或错误报告,它不会验证输入格式。例如。它仍然会阅读这个邪恶的烂摊子并接受它:

[parameters] numColors=999 boardSize=999 numSnails=999
[color] id=0 nextColor=1 deltaOrientation=+2 [color] id=1 nextColor=2
                         deltaOrientation=+1 [
initialization] id=1 row=5 col=0 orientation=1
[color] id=2 nextColor=3 deltaOrientation=-2
[parameters] numColors=4 boardSize=11 numSnails=2
[color] id=3 nextColor=0 deltaOrientation=-1
[initialization] id=0 row=3 col=4 orientation=0

如果您的输入格式不稳定并且是计算机编写的,我强烈建议您不要使用标准库方法,因为它会导致难以诊断的问题和糟糕的用户体验(不要让您的用户想要抛出由于“无法解析游戏数据”等无用的错误消息,他们的计算机离开了窗口。

否则,你可能会。一方面,编译速度会更快。

答案 1 :(得分:1)

它不是什么

简而言之,这根本不是 INI 格式。它只是非常松散地相似。哪个不错。

它是什么?

你没有具体说明很多,所以我会做一些假设。

为了简单起见,我将假设

  • 初始化部分在颜色部分之前
  • 相同部分中的键始终具有相同的顺序
  • 在类似部分中显示的所有键都是必需的
  • 增量是有符号整数值(正号是可选的)
  • 所有其他值都是非负整数
  • 空格不重要
  • 案例很重要
  • 所有数字都是十进制形式(不考虑前导零)

非必要的扣除(可用于添加更多验证):

  • 初始化次数 = numSnails
  • 板尺寸决定行和列在 [0, boardSize) 中

数据结构

为了表示文件,我会:

namespace Ast {
    struct Initialization {
        unsigned id, row, col, orientation;
    };

    struct Color {
        unsigned id, nextColor;
        int deltaOrientation;
    };

    struct File {
        unsigned numColors, boardSize, numSnails;

        std::vector<Initialization> initializations;
        std::vector<Color>          colors;
    };
}

这是我能想到的最简单的。

解析

对 Boost Spirit 来说是一份不错的工作。如果我们将数据结构调整为融合序列:

BOOST_FUSION_ADAPT_STRUCT(Ast::Initialization, id, row, col, orientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::Color, id, nextColor, deltaOrientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::File, numColors, boardSize, numSnails,
                          initializations, colors)

我们基本上可以让解析器“自己写”:

template <typename It>
struct GameParser : qi::grammar<It, Ast::File()> {
    GameParser() : GameParser::base_type(start) {
        using namespace qi;
        start = skip(blank)[file];

        auto section = [](std::string name) {
            return copy('[' >> lexeme[lit(name)] >> ']' >> (+eol | eoi));
        };
        auto required = [](std::string name) {
            return copy(lexeme[eps > lit(name)] > '=' > auto_ >
                        (+eol | eoi));
        };

        file =
            required("numColors") >
            required("boardSize") >
            required("numSnails") >
            *initialization >
            *color >
            eoi; // must reach end of input

        initialization = section("initialization") >
            required("id") >
            required("row") >
            required("col") >
            required("orientation");
            
        color = section("color") >
            required("id") >
            required("nextColor") >
            required("deltaOrientation");

        BOOST_SPIRIT_DEBUG_NODES((file)(initialization)(color))
    }

  private:
    using Skipper = qi::blank_type;
    qi::rule<It, Ast::File()>                    start;
    qi::rule<It, Ast::File(), Skipper>           file;
    qi::rule<It, Ast::Initialization(), Skipper> initialization;
    qi::rule<It, Ast::Color(), Skipper>          color;
};

由于我们做出了许多假设,我们在这个地方散布了期望点operator> 序列,而不是 operator>>)。这意味着我们会收到无效输入的“有用”错误消息,例如

Expected: nextColor
Expected: =
Expected: <eoi>
<块引用>

另请参阅下面的奖励部分,可以大大改善这一点

测试/现场演示

测试它,我们将首先读取文件,然后使用该解析器解析它:

std::string read_file(std::string name) {
    std::ifstream ifs(name);
    return std::string(std::istreambuf_iterator<char>(ifs), {});
}

static Ast::File parse_game(std::string_view input) {
    using SVI = std::string_view::const_iterator;
    static const GameParser<SVI> parser{};

    try {
        Ast::File parsed;
        if (qi::parse(input.begin(), input.end(), parser, parsed)) {
            return parsed;
        }
        throw std::runtime_error("Unable to parse game");
    } catch (qi::expectation_failure<SVI> const& ef) {
        std::ostringstream oss;
        oss << "Expected: " << ef.what_;
        throw std::runtime_error(oss.str());
    }
}

很多可以改进,但现在它可以工作并解析您的输入:

Live On Coliru

int main() {
    std::string game_save = read_file("input.txt");

    Ast::File data = parse_game(game_save);
}

没有输出就意味着成功。

奖金

一些改进,而不是使用 auto_ 为类型生成正确的解析器,我们可以明确:

namespace Ast {
    using Id          = unsigned;
    using Size        = uint8_t;
    using Coord       = Size;
    using ColorNumber = Size;
    using Orientation = Size;
    using Delta       = signed;

    struct Initialization {
        Id          id;
        Coord       row;
        Coord       col;
        Orientation orientation;
    };

    struct Color {
        Id          id;
        ColorNumber nextColor;
        Delta       deltaOrientation;
    };

    struct File {
        Size numColors{}, boardSize{}, numSnails{};

        std::vector<Initialization> initializations;
        std::vector<Color>          colors;
    };
}  // namespace Ast

然后在解析器中定义类似的:

qi::uint_parser<Ast::Id>          _id;
qi::uint_parser<Ast::Size>        _size;
qi::uint_parser<Ast::Coord>       _coord;
qi::uint_parser<Ast::ColorNumber> _colorNumber;
qi::uint_parser<Ast::Orientation> _orientation;
qi::int_parser<Ast::Delta>        _delta;

然后我们使用例如:

initialization = section("initialization") >
    required("id", _id) >
    required("row", _coord) >
    required("col", _coord) >
    required("orientation", _orientation);

现在我们可以改进错误信息,例如:

input.txt:2:13 Expected: <unsigned-integer>
 note: boardSize = (11)
 note:             ^--- here

input.txt:16:19 Expected: <alternative><eol><eoi>
 note:     nextColor = 1 deltaOrientation = +2
 note:                   ^--- here

完整代码,Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/home/qi.hpp>
#include <fstream>
#include <sstream>
#include <iomanip>
namespace qi = boost::spirit::qi;

namespace Ast {
    using Id          = unsigned;
    using Size        = uint8_t;
    using Coord       = Size;
    using ColorNumber = Size;
    using Orientation = Size;
    using Delta       = signed;

    struct Initialization {
        Id          id;
        Coord       row;
        Coord       col;
        Orientation orientation;
    };

    struct Color {
        Id          id;
        ColorNumber nextColor;
        Delta       deltaOrientation;
    };

    struct File {
        Size numColors{}, boardSize{}, numSnails{};

        std::vector<Initialization> initializations;
        std::vector<Color>          colors;
    };
}  // namespace Ast

BOOST_FUSION_ADAPT_STRUCT(Ast::Initialization, id, row, col, orientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::Color, id, nextColor, deltaOrientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::File, numColors, boardSize, numSnails,
                          initializations, colors)

template <typename It>
struct GameParser : qi::grammar<It, Ast::File()> {
    GameParser() : GameParser::base_type(start) {
        using namespace qi;
        start = skip(blank)[file];

        auto section = [](const std::string& name) {
            return copy('[' >> lexeme[lit(name)] >> ']' >> (+eol | eoi));
        };
        auto required = [](const std::string& name, auto value) {
            return copy(lexeme[eps > lit(name)] > '=' > value >
                        (+eol | eoi));
        };

        file =
            required("numColors", _size) >
            required("boardSize", _size) >
            required("numSnails", _size) >
            *initialization >
            *color >
            eoi; // must reach end of input

        initialization = section("initialization") >
            required("id", _id) >
            required("row", _coord) >
            required("col", _coord) >
            required("orientation", _orientation);
            
        color = section("color") >
            required("id", _id) >
            required("nextColor", _colorNumber) >
            required("deltaOrientation", _delta);

        BOOST_SPIRIT_DEBUG_NODES((file)(initialization)(color))
    }

  private:
    using Skipper = qi::blank_type;
    qi::rule<It, Ast::File()>                    start;
    qi::rule<It, Ast::File(), Skipper>           file;
    qi::rule<It, Ast::Initialization(), Skipper> initialization;
    qi::rule<It, Ast::Color(), Skipper>          color;

    qi::uint_parser<Ast::Id>          _id;
    qi::uint_parser<Ast::Size>        _size;
    qi::uint_parser<Ast::Coord>       _coord;
    qi::uint_parser<Ast::ColorNumber> _colorNumber;
    qi::uint_parser<Ast::Orientation> _orientation;
    qi::int_parser<Ast::Delta>        _delta;
};

std::string read_file(const std::string& name) {
    std::ifstream ifs(name);
    return std::string(std::istreambuf_iterator<char>(ifs), {});
}

static Ast::File parse_game(std::string_view input) {
    using SVI = std::string_view::const_iterator;
    static const GameParser<SVI> parser{};

    try {
        Ast::File parsed;
        if (qi::parse(input.begin(), input.end(), parser, parsed)) {
            return parsed;
        }
        throw std::runtime_error("Unable to parse game");
    } catch (qi::expectation_failure<SVI> const& ef) {
        std::ostringstream oss;

        auto where  = ef.first - input.begin();
        auto sol    = 1 + input.find_last_of("\r\n", where);
        auto lineno = 1 + std::count(input.begin(), input.begin() + sol, '\n');
        auto col    = 1 + where - sol;
        auto llen   = input.substr(sol).find_first_of("\r\n");

        oss << "input.txt:" << lineno << ":" << col << " Expected: " << ef.what_ << "\n"
            << " note: " << input.substr(sol, llen) << "\n"
            << " note:"  << std::setw(col) << "" << "^--- here";
        throw std::runtime_error(oss.str());
    }
}

int main() {
    std::string game_save = read_file("input.txt");

    try {
        Ast::File data = parse_game(game_save);
    } catch (std::exception const& e) {
        std::cerr << e.what() << "\n";
    }
}

在此处查看各种故障模式和 BOOST_SPIRIT_DEBUG 输出:

enter image description here

相关问题