読者です 読者をやめる 読者になる 読者になる

ゆとりーなの日記

日記的な事を書いて行くと思はれる

日本語フィーバー開発

麻雀用語をC++の識別子にするとき結構単語考えるの面倒なんで日本語識別子使えんかなーとか甘えたこと考えていたらこんなページGCCで日本語識別子を使う | 株式会社きじねこが見つかりました。
適当に要約するとユニバーサル文字が実は使えるってんで前処理で日本語識別子をユニバーサル文字に変換すれば良さげという話です。
で、やってみたんですが案外使えない文字が多いんですね。全角数字とか①とか?とかが使えなくて納得がいかなかったわけです。
そこでどうせデバッガとか使わないし別にユニバーサル文字にする必要はないのではないかと気づいてしまいます。適当に先頭にタグ付けてユニバーサル文字の数値の部分を後ろに繋げれば甘えられそうです。
というわけで作った変換プログラムがこちら。

#include <cstdio>
#include <clocale>
#include <cwchar>
 
int main(void) {
  std::wint_t wc;
  std::setlocale(LC_CTYPE, "");
  while ((wc = std::getwchar()) != WEOF) {
    if (wc < 0x80) {
      std::putwchar(wc);
    } else {
      std::wprintf(L"fvmjutf%04x", wc);
    }
  }
}

単純に日本語が来たらfvmjutfの後ろにユニバーサル文字の数値の部分を繋げて出力するというものです。これを通してからコンパイルすればまぁ変な文字はないので通るでしょう。
で、変な識別子が作られているのでエラーメッセージが読みにくいんですね。というわけでエラーメッセージを読んでfvmjutfを見つけたら後ろの4桁の16進数をユニバーサル文字の数値に変換して出力する変換プログラムもついでに作るわけです。

#include <cstdio>
#include <clocale>
#include <cwchar>
#include <sstream>
#include <Windows.h>
 
int main(void) {
  std::wint_t wc;
  int cnt = 0;
  std::setlocale(LC_CTYPE, "");
  const wchar_t l[] = L"fvmjutf";
  while ((wc = std::getwchar()) != WEOF) {
    if (!cnt && wc == L'f') {
      ++cnt;
    } else if (cnt == 1 && wc == L'v') {
      ++cnt;
    } else if (cnt == 2 && wc == L'm') {
      ++cnt;
    } else if (cnt == 3 && wc == L'j') {
      ++cnt;
    } else if (cnt == 4 && wc == L'u') {
      ++cnt;
    } else if (cnt == 5 && wc == L't') {
      ++cnt;
    } else if (cnt == 6 && wc == L'f') {
      wchar_t s[5];
      for (int i = 0; i < 4; ++i) {
        s[i] = std::getwchar();
      }
      s[4] = L'\0';
      std::wstringstream str;
      str << s;
      std::wint_t m;
      str >> std::hex >> m;
      std::putwchar(m);
      cnt = 0;
    } else {
      for (int i = 0; i < cnt; ++i) {
        std::putwchar(l[i]);
      }
      std::putwchar(wc);
      cnt = 0;
    }
  }
}

こっちもfvmjutfを見つけたら後ろの16進数文字列を数値に変換することをだらだら書いただけです。
前者のプログラムをconv.exe、後者をconv2.exeとしてMakefileには以下のようなルールを書いておくわけです。

.cc.o:
	$(CC) $(CFLAGS) $(CXXFLAGS) -E $< | conv | $(CC) $(CFLAGS) $(CXXFLAGS) -c -x c++ - -o $@ 2>&1 | conv2

先ずgccプリプロセスを行い、それの結果をパイプしてconvに送ります。そうすると日本語識別子が怪しい英数字だけの識別子になるのでそれをコンパイルします。で、標準エラー出力をパイプしてconv2に送るのでコンパイルエラーがある場合はそれの文字呼んで怪しい英数字だけの識別子を日本語識別子に戻します。これでただでさえ暗号なエラーメッセージが読みやすくなるわけです。
これを使うことでこのような怪しいコードでフィーバー秋刀魚が開発出来るようになってしまいました。

#pragma once
#include <cstdint>
#include <array>
#include "定數.hpp"
#include "麻雀牌.hpp"
#include "../風/風.hpp"

namespace フィーバー秋刀魚 { namespace モデル { namespace 牌 {
constexpr bool 數牌?(麻雀牌 牌) {
  return 牌 <= 麻雀牌::9索;
}

constexpr bool 字牌?(麻雀牌 牌) {
  return !數牌?(牌);
}

constexpr bool 中張牌?(麻雀牌 牌) {
  return 中張牌 & 牌;
}

constexpr bool 役牌?(麻雀牌 牌) {
  return 牌 >= 麻雀牌::北;
}

constexpr bool 場風?(麻雀牌 牌) {
  return 牌 == 麻雀牌::東;
}

constexpr bool 自風?(麻雀牌 牌, 風::風 風) {
  return static_cast<std::uint32_t>(牌) - static_cast<std::uint32_t>(麻雀牌::東) == static_cast<std::uint32_t>(風);
}

constexpr 麻雀牌 ドラ(麻雀牌 牌) {
  return 牌 == 麻雀牌::一萬 ? 麻雀牌::九萬 :
         牌 == 麻雀牌::九萬 ? 麻雀牌::一萬 :
         牌 == 麻雀牌::⑨筒 ? 麻雀牌::①筒 :
         牌 == 麻雀牌::9索 ? 麻雀牌::1索 :
         牌 == 麻雀牌::北 ? 麻雀牌::東 :
         牌 == 麻雀牌::中 ? 麻雀牌::白 :
         static_cast<麻雀牌>(static_cast<std::uint32_t>(牌) + 1);
}

constexpr std::array<麻雀牌, 牌の種類> 範圍() {
  return {{麻雀牌::一萬, 麻雀牌::九萬,
           麻雀牌::①筒, 麻雀牌::②筒, 麻雀牌::③筒, 麻雀牌::④筒, 麻雀牌::⑤筒,
           麻雀牌::⑥筒, 麻雀牌::⑦筒, 麻雀牌::⑧筒, 麻雀牌::⑨筒,
           麻雀牌::1索, 麻雀牌::2索, 麻雀牌::3索, 麻雀牌::4索, 麻雀牌::5索,
           麻雀牌::6索, 麻雀牌::7索, 麻雀牌::8索, 麻雀牌::9索,
           麻雀牌::東, 麻雀牌::南, 麻雀牌::西, 麻雀牌::北,
           麻雀牌::白, 麻雀牌::發, 麻雀牌::中}};
}
}}}

かなり怪しいです。はい。