問題の名前 : アンエスケープ
問題 : 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++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 版とほぼ同じ。もっと長くなると思っていたのでびっくりした。
まあ、レキサが別関数になっていないからか。