--- breaks: false tags: public-tech --- # ある Unicode 文字が Shift_JIS(or CP932)で表現できるかを巨大 switch 文で調べる 人生をやっていると、ある Unicode 文字が Shift_JIS で表現できるかどうかを調べたくなることがある。大抵こういうのは言語の標準ライブラリでカバーされていたりするから悩まないのだが、残念ながら Elixir にはそういうのが無いっぽかったので、作った。 ## 何をやるか Unicode の code point を表す一つの整数値が与えられるので、その文字が SJIS で表現できるかを調べる。例えば「あ」(U+3042)では `0x3042` が与えられて `true` を返すし、「髙」(U+9AD9)では `0x9ad9` が与えられて `false` を返す。 ## Shift_JIS と SJIS と CP932 と MS932 と Windows-31J どれも文字コードを表す名称だが、指すものが微妙に違う。詳細は[ここ](http://una.soragoto.net/topics/13.html)などを参照してほしい。大雑把に言うと Shift_JIS を拡張したものが CP932 で、CP932, MS932, Windows-31J は同じものと思って良さそう。やっかいなのは SJIS で、Shift_JIS の別名かと思いきや、[Ruby では CP932 を指すらしい](https://developers.freee.co.jp/entry/sjis-is-not-an-alias-of-shift-jis)[^ruby-cp932]。ここでは CP932 をターゲットにする。 [^ruby-cp932]: そうはならんやろ。 ## 方針 あらかじめ Unicode code point のうち CP932 に存在する値だけを選び出しておき、これらを使って巨大な分岐を書く。入力された Unicode code point をこの分岐に通すことで判定を行う。あとに示すようにこの分岐は4000通り以上になるので、パフォーマンスの観点から Elixir でそのまま書かずに C で書いて NIF として使うことにする。 結局、C で巨大 switch 文を書けばよい。 ## 巨大 switch 文を作る [ここ](http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP932.TXT)に Unicode と CP932 の対応表が plain text で置いてあるので、これをとってきて、適当にシェルを叩いて `case` 文に変形する。 ``` $ curl http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP932.TXT | \ egrep -v '^#'| \ awk -F "\t" '{ print $2 }' | \ egrep -v '^\s*$' | \ awk '{ print strtonum($0) }' | \ sort -nu | \ awk '\ BEGIN{p=-1; printf "0"} \ { if($0==p+1){p += 1}else{printf "--%d\n", p;printf $0;p=$0}} \ END { printf "--%d\n", p }' | \ sed -E 's/^([0-9]+)--([0-9]+)$/case \1 ... \2:/' \ > check_sjis.c ``` 実行すると `check_sjis.c` というファイルができて、その中に次のように出力される。4247行ある。ちなみに `case n ... m` という表記は GCC 拡張である([Case Ranges](https://gcc.gnu.org/onlinedocs/gcc/Case-Ranges.html))[^case-ranges]。 ``` case 0 ... 127: case 167 ... 168: case 176 ... 177: case 180 ... 180: case 182 ... 182: case 215 ... 215: case 247 ... 247: case 913 ... 929: case 931 ... 937: case 945 ... 961: (中略) case 40845 ... 40845: case 40853 ... 40853: case 40860 ... 40861: case 40864 ... 40864: case 63785 ... 63785: case 63964 ... 63964: case 64014 ... 64045: case 65281 ... 65374: case 65377 ... 65439: case 65504 ... 65509: ``` [^case-ranges]: GCC 拡張なんか使わずに `case X` を並べればええやんというのはその通りなのだが、これを作ったときの自分は `case n ... m` が C11 から導入されたつもりでいたのだった。全然そんなことなかった。 あとはこの前後にいい感じに C コードを書くとちゃんと動く。ちゃんと動く例は[ここ](https://github.com/ushitora-anqou/daidoquer2/commit/869ae6f55b0d8b51c286b5f4b648511479ac47d5)にある。 ## おまけ:Shift_JIS を使いたい場合 対応表が[ここ](http://x0213.org/codetable/sjis-0213-2004-std.txt)にあるので、似たようにやる。 ``` $ curl http://x0213.org/codetable/sjis-0213-2004-std.txt | \ egrep -v '^#'| \ awk '{ print $2 }' | \ awk 'match($0,/^U\+([0-9A-F]+)$/,a){ print strtonum("0x" a[1])}' | \ sort -n | \ awk '\ BEGIN{p=-1; printf "0"} \ { if($0==p+1){p += 1}else{printf "--%d\n", p;printf $0;p=$0}} \ END { printf "--%d\n", p }' | \ sed -E 's/^([0-9]+)--([0-9]+)$/case \1 ... \2:/' \ > check_sjis.c ``` ## まとめ これ本当にこんな泥臭いことしないといけないのかなぁ。いや結局誰かがやらないといけないとは思うのだが。 ## [2023/10/15 追記] OCaml で同じことをやる OCaml でも同じことをする必要が出てきたので、次のようなスクリプトを書いた。 ``` curl http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP932.TXT | \ egrep -v '^#'| \ awk -F "\t" '{ print $2 }' | \ egrep -v '^\s*$' | \ awk '{ print strtonum($0) }' | \ sort -nu | \ awk '{ printf "| " $1 "\n" }' ```
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.