Gauche練習帳 文字コード変換

公式配布のMinGW版Gaucheは内部エンコーディングUTF-8としてビルドされている。OSやロケールに依存しないという点でUTF-8は扱いやすいんだけど、WindowsコマンドプロンプトSJISを前提としているため以下のシンプルなプログラムでも文字化けしてしまう。

(print "こんにちは、世界")

解決方法としてはコマンドプロンプトをUTF-8にする方法がある。ただ、これをやるとこんどは日本語入力がしづらくなってしまう。

というわけで、プログラム側でSJISに変換して出力するようにしてみた。
sjis.scm

(use gauche.charconv)
(define-macro (sjis . body)
  `(with-ports
    (wrap-with-input-conversion
     (current-input-port)
     "shift_jis")
    (wrap-with-output-conversion
     (current-output-port)
     "shift_jis")
    (wrap-with-output-conversion
     (current-error-port)
     "shift_jis")
    (lambda () ,@body)))

with-ports関数で入力ポートも出力ポートもエラーポートもSJISとして変換設定するだけのマクロ。

使い方はこう。

(load "./sjis")
(sjis
  (print "こんにちは、世界")
)

文字コード変換関係の関数は他にもいろいろあって、ces-guess-from-string関数を使うと文字コードを自動判別してくれるらしい。ならばNKFみたいなことができそうだ。
と思ってやってみたら意外と難しかった。Gaucheは内部エンコーディングと異なる文字コードのファイルを普通に読み込むとエラーになってしまう。そのため文字列を判別関数や変換関数に渡すところまでなかなかたどり着けない。いろいろ考えた結果、入力ポートからu8vectorとしてブロック単位で読み込んではEOFまでひたすら文字列ポートに出力する、というport->incomplete-string関数を書いてみた。これでとりあえずファイルイメージのままの不完全文字列として保持できるので変換関数に渡せる。ファイル全部をメモリに読み込むから巨大なファイル処理にはいまいちだけど。

(use gauche.charconv)
(use gauche.uvector)

(define (nkf encode)
  (define (port->incomplete-string port)
    (let ((strport (open-output-string))
	  (u8buf (make-u8vector 4096)))
      (let loop ((len (read-block! u8buf port)))
	(cond ((eof-object? len)
	       (get-output-string strport))
	      (else
	       (write-block u8buf strport 0 len)
	       (loop (read-block! u8buf port)))))))
  (let ((s (port->incomplete-string (current-input-port))))
    (display
     (ces-convert s (ces-guess-from-string s "*jp") encode))))

;===========================================
(use gauche.parseopt)

(define (main args)
  (let-args (cdr args)
	    ((jis    "j|jis")
	     (euc    "e|euc")
	     (utf8   "u|utf8")
	     (sjis   "s|sjis"))
	    (cond (jis  (nkf "iso2022jp"))
		  (euc  (nkf "euc_jp"))
		  (utf8 (nkf "utf-8"))
		  (else (nkf "shift_jis")))))

main関数を用意してフィルタープログラムにしてみた。入力コードは自動判別、出力コードは起動引数で指定して-jならJIS、-eならEUC_JPみたいな感じ。

この間作ったsort.scm、uniq.scmと組み合わせてこんな使い方ができる。