オフラインリアルタイムどう書く E29 の実装例( C++ )

問題の名前 : アンエスケープ
問題 : http://nabetani.sakura.ne.jp/hena/orde29unes/
実装リンク集 : https://qiita.com/Nabetani/items/f2db9b916c0a301b744f

次回のイベントは  2/2
see https://yhpg.doorkeeper.jp/events/84247 。

で。

ruby に **racc** があるのなら、C++ には **boost::spirit::qi** がある。

というわけで、qi で書いてみた。

非常に苦しんだけど、なんとか書くことができた。

```c++:c++17
// clang++ -std=c++17 -O2 -Wall e29.cpp
#include <iostream>
#include <vector>
#include <string>
#include <optional>
#include <algorithm>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/qi_action.hpp>
#include <boost/algorithm/string/join.hpp>

namespace qi = boost::spirit::qi;
namespace sw = qi::standard_wide;

template <typename Iterator>
std::optional<std::vector<std::string>> parse(Iterator first, Iterator last)
{
  std::vector<std::string> o{""};
  using rule_type = qi::rule<Iterator, std::string()>;
  rule_type alnum_unit = (sw::char_ - '/' - '\'' - '"');
  rule_type slash_pair = qi::lit("//");
  auto raw_proc = [&o](auto const &v) {
    o.back() += v;
  };
  auto slash_pair_proc = [&o](auto const &v) {
    o.back() += "/";
  };
  rule_type sq = qi::lit('\'');
  rule_type dq = qi::lit('"');
  rule_type slash = qi::lit('/');
  rule_type dq_unit = "\"" >> *(alnum_unit | sq | slash) >> "\"";
  rule_type sq_unit = "'" >> *(alnum_unit | dq | slash) >> "'";
  rule_type entry = +(
      alnum_unit[raw_proc] | slash_pair[slash_pair_proc] | dq_unit[raw_proc] | sq_unit[raw_proc]);
  auto entry_proc = [&o](auto const &v) {
    o.emplace_back("");
  };
  rule_type entries = entry[entry_proc] % '/';
  bool r = qi::parse(first, last, entries);
  auto rest = std::string(first, last);
  if (!r || first != last)
  {
    return std::nullopt;
  }
  o.pop_back(); // なぜこれが必要なのかわからない。
  bool empty_exist = std::any_of(
      o.cbegin(), o.cend(),
      [](auto const &v) -> bool { return v.empty(); });
  if (empty_exist)
  {
    return std::nullopt;
  }
  return o;
}

std::string solve(std::string const &src)
{
  auto result = parse(src.cbegin(), src.cend());
  if (!result)
  {
    return "-";
  }
  return boost::algorithm::join(*result, ",");
}

struct result_t
{
  int win, lose;
  int total() const { return win + lose; }
};

void run_test(result_t &r, char const *src, char const *expected)
{
  std::string actual = solve(src);
  bool okay = actual == expected;
  (okay ? r.win : r.lose) += 1;
  std::cout
      << (okay ? "ok " : "**NG** ")
      << src << "->" << actual << " / " << expected
      << std::endl;
}

int main()
{
#define test(x, y) run_test(result, x, y)
  result_t result = {0};
  /*0*/ test("foo/bar/baz", "foo,bar,baz");
  /*1*/ test("/foo/bar/baz'/", "-");
  /*2*/ test("\"", "-");
  /*3*/ test("'", "-");
  // 中略
  /*63*/ test("Foo/Bar/\"Hoge'/'Fuga\"", "Foo,Bar,Hoge'/'Fuga");
  std::cout << result.win << " / " << result.total() << std::endl;
  return 0;
}
```

テストデータの大半は省略。

`o.pop_back();` が必要な理由がよくわからない。

というかそもそも、`[&o]` とキャプチャするのは美しくない気がするんだけど、他に方法がわからなかった。

初めて boost::spirit::qi を使ったんだけど：

* 噂通り、コンパイル時間が boost した。
* 噂通り、演算子のオーバーロード濫用していて面白くも気持ち悪いライブラリだと思った。

という感じ。流石です。`+(expr1|expr2)` とかがなんかすごい。

生まれて初めてといえば、 `std::optional` を使ったのも初めて。`boost::optional` なら使ったことあったけど。`std::none` じゃなくて `std::nullopt` なんだね。

行数という点では、solve の最後までで 63行。[racc 版]( https://qiita.com/Nabetani/items/6cccc409939c689374b5 )とほぼ同じ。もっと長くなると思っていたのでびっくりした。

まあ、レキサが別関数になっていないからか。