wxHaskell (その3)

前回積み残しのコンソールが出てしまう問題だが、
PEファイルのフォーマットを調べるとSubsystemのフラグに
それっぽいものが有ったのでいじってみると見事出なくなった。
これにて解決である。(これで良いのか…?)
後学の為に場所を記しておく。
(こんなとこだけ記されてもうれしくない方はちゃんとしたところで
PEファイルのフォーマットを調べましょう)



ここ(アドレスDCh)の2バイトが0002hだとWindowsGUIアプリということのようである。

(追記:
リンカのオプションにオプションがあるようです…。
ですので、上のような書き換えをせずとも
ghc -optl -mwindows で良いようです)


続いて根本的問題のように思える日本語について考える。
そもそもなぜ日本語が使えないかというと、GHCSJIS
受け付けてくれないからなのである。
プログラムの動作としては、Stringは[Char]だし、
Charは単なる0..255なデータだと思うので、日本語を扱うのは全く問題ない。
ところがどっこい、GHCは文字リテラルとして想定外のものを
はじいているようなのである。(というかパーズできないのか)
"lexical error in string/character literal" などというメッセージを出して
コンパイルエラーとなる。
このエラーは文字エンコードSJISにしてもUTF8にしても発生する。
しかし、EUC-JPにするとなぜか発生しない。
EUC-JPが80h〜A0hの領域を使っていないせいなのだろうか。

(追記2:
Charは一バイト文字じゃないようです。
ghc 6.2.1だとmaxBound::Charが\x10ffffになっています。
手持ちのHugs(Version Nov 2003)だと\xffです。
Haskellの仕様では上界は未定?あるいはUnicodeを扱うように決められてるけど
Hugsが実装していないのか?(ちゃんと仕様書読めという話ですが…)
しかし、GHCにしても入力はUnicodeに変換されないみたいだし、
FFI周りも単にバイトストリームとして扱われてしまっているようなので
下記のような工夫は必要)

とにもかくにもひとつの光明が見えてきた。EUC-JPを使うのである。
EUC-JPなら問題なくコンパイルできる。
そして、Windowsが受け付けるのはSJISである。
(正確に言うとWindowsに言及するのはおかしい。
wxHaskellが何を受け付けるかというのが問題なのであるが、
wxHaskellはおそらくGHCと同じで多倍長文字について
何にも考えていないだろうから、ポータビリティを考えつつ
日本語の問題に言及するのは不可能である。
そのためここではWindows環境下に限っての議論を行う。
wxHaskellがWindowsAPIに何も考えず文字列を流し込むという前提であるが。
Unix系をだと多分デフォルトがEUC-JPなので
もっと何も考えないで良いと思う)
リテラルに格納されるのはGHCが許すEUC-JPとなる。
つまりtextとかの属性に文字列を渡す前に
EUC-JP→SJIS変換を掛ければ良いのである。

  [title,fileMenu,exitMenu] =
    map eucToSjis ["電卓","ファイル (&X)","終了 (&X)"]
  ...
  f      <- frameFixed    [text := title, ...
  p      <- panel f       []
  file   <- menuPane      [text := fileMenu]
  mclose <- menuItem file [text := exitMenu]
  ...

このファイルを 「EUC-JPで保存」し、コンパイルして実行すると、
title,fileMenuなどには「SJIS」の文字列が入ることになる。

eucToSjisは次のように適当に作った。
あんまりちゃんと正しいか確かめてない。

module CCode where

import Data.Char

eucToSjis :: String -> String
eucToSjis  = 
eucToSjis (x:xs)
  | ord x <= 0xA0 = x:eucToSjis xs
  | otherwise = case xs of
      y:ys -> (chr $ cvt1 (ord x)) :
              (chr $ cvt2 (ord x) (ord y)) :
              eucToSjis ys
      _    -> error "invalid EUC-JP string."
  where
    cvt1 x | x < 0xdf  = ((x+1) `div` 2) + 0x30
           | otherwise = ((x+1) `div` 2) + 0x70

    cvt2 x y | y <= 0xA0    = y
             | mod x 2 == 0 = y-2
             | y < 0xe0     = y-0x61
             | otherwise    = y-0x60

ちなみに上プログラムは
http://www.net.is.uec.ac.jp/~ueno/material/kanji/euc2sjis.html
このページを参考にさせていただいた。


で、取り敢えず昨日のソースをこのように変更し、
いそいそとコンパイル→実行したところ正しく日本語が表示された。
なんとかうまくいったようである。


しかーし。
上記のソースはいくつかの点でいや〜んな感じなのである。
いくつかの点というか、

あたりであるが。


何が問題って、原因はすべてGHCが多倍長文字を含むソースを
コンパイルできないことにあるんだから、
もっと直接的な解決方法がある。バイナリ書き換えである。
生成された実行ファイルから文字列を検索し、しれしれっと
SJISの文字列に書き換えればプログラムは何事も無かったかのように
日本語を表示してくれる。
だがしかし、これもどうなのだ、っちゅう感じである。
そもそもソースコード中で文字列について言及できてないのがなんともかんとも。
全然駄目ですな。


違う方法を考える。
発想は前と同じでソース中には多倍長文字を記述しないという方式(…?)である。
国際化のことなんかなーんも考えてないGHCに多倍長文字食わすことが
そもそもの間違い(?)だというわけで。
ソースに書けないものはどっかから引っ張ってこればよい。
なにか別のファイルにSJISで文字書いておけば実行時に
そのファイルを読み込むことによりSJISの文字列が取得できる。

 -- 文字列リソース
initResource :: String -> IO (String -> String)
initResource resfile = do
  resDat <- readFile resfile
  let dat = map (\(a:b:_) -> (a,b)) $
            takeWhile (not.null)    $
            iterate (drop 2)        $ lines resDat
  return (\name -> fromMaybe name $ lookup name dat)

上関数は何かファイルを指定して、ID→文字列への写像を返すような
コマンドになる。

  ...
  res    <- initResource "res.txt"

  f      <- frameFixed    [text := res "title", ... 
  p      <- panel f       []
  file   <- menuPane      [text := res "file"]
  mclose <- menuItem file [text := res "exit"]
  ...

使い方はこんな感じ。

title
超関数型電卓
file
超ファイル (&F)
exit
超終了 (&X)

res.txtには上のようなことをおずおずと書き立てておく。
で、コンパイル&実行。



おお、やったぜ。
これでようやく枕を高くして眠れるというわけである。
ちなみにこれだと文字列をプラットフォームごとに用意することにより
ポータビリティを持たせることが可能になる。
また、これはローカライズに普通に用いられる手法なので
他言語対応もできる。
問題点としてはファイルを別に用意する必要があるということか。
リソースに埋め込めれば理想的なのだが
私はいまのところHaskellでリソース扱う方法を知らないし、
ポータビリティも失われそうである。


まぁ、結局何が良いかよく分からないが、
個人的には最後のが比較的まともなのではないかと思う。
一応最後のやつのソース+Windowsバイナリを。
(圧縮+サブシステム書き換えで割かしまともにしたやつ)
http://fxp.hp.infoseek.co.jp/haskell/calc2.zip


どうでも良いけど、このページHaskellばっかりだなぁ。