LISPUSER

LISPMEMOLisp is like a ball of mud - you can throw anything you want into it, and it's still Lisp. -- Anonymous

(top)  (memo)  (rss)

CLISP と日本語メモ

Implementation Notes for CLISP の 30.5 Encodings の翻訳とサンプルなど.

ext:convert-string-to-bytes 文字列からバイト列への変換
ext:convert-string-from-bytes バイト列から文字列への変換

翻訳記事は CLISP のページ を参照してください.

サンプルは,以下の通りです.

文字列のバイト/ビット表現を表示する

(defun describe-string (string encoding)
  (flet ((char-list (char)
           (coerce
            (ext:convert-string-to-bytes
             (make-string 1 :initial-element char)
             encoding)
            'list)))
    (loop for ch across string
          for bits = (char-list ch)
          do (format t "~A : ~{~2,'0X ~}: ~{~8,'0b ~}~&" ch bits bits))))

以下は SLIME によるセッションの例です.UTF-8 と EUC-JP を比較すると,違いが よくわかると思います.

CL-USER> (defun describe-string (string encoding)
  (flet ((char-list (char)
           (coerce
            (ext:convert-string-to-bytes
             (make-string 1 :initial-element char)
             encoding)
            'list)))
     (loop for ch across string
           for bits = (char-list ch)
           do (format t "~A : ~{~2,'0X ~}: ~{~8,'0b ~}~&" ch bits bits))))

describe-string
CL-USER> (describe-string "日本語文字列" charset:utf-8)
日 : E6 97 A5 : 11100110 10010111 10100101 
本 : E6 9C AC : 11100110 10011100 10101100 
語 : E8 AA 9E : 11101000 10101010 10011110 
文 : E6 96 87 : 11100110 10010110 10000111 
字 : E5 AD 97 : 11100101 10101101 10010111 
列 : E5 88 97 : 11100101 10001000 10010111 

nil
CL-USER> (describe-string "日本語文字列" charset:euc-jp)
日 : C6 FC : 11000110 11111100 
本 : CB DC : 11001011 11011100 
語 : B8 EC : 10111000 11101100 
文 : CA B8 : 11001010 10111000 
字 : BB FA : 10111011 11111010 
列 : CE F3 : 11001110 11110011 

nil
CL-USER> 

が,この関数に欠点を発見しました…… iso-2202-jp などエスケープシーケン スによる切り替えの入る(CLISP 用語だとステートフルな)エンコーディングだと

CL-USER> (describe-string "日本語文字列" charset:iso-2022-jp)
日 : 1B 24 42 46 7C 1B 28 42 : 00011011 00100100 01000010 01000110 01111100 \
00011011 00101000 01000010 
本 : 1B 24 42 4B 5C 1B 28 42 : 00011011 00100100 01000010 01001011 01011100 \
00011011 00101000 01000010 
語 : 1B 24 42 38 6C 1B 28 42 : 00011011 00100100 01000010 00111000 01101100 \
00011011 00101000 01000010 
文 : 1B 24 42 4A 38 1B 28 42 : 00011011 00100100 01000010 01001010 00111000 \
00011011 00101000 01000010 
字 : 1B 24 42 3B 7A 1B 28 42 : 00011011 00100100 01000010 00111011 01111010 \
00011011 00101000 01000010 
列 : 1B 24 42 4E 73 1B 28 42 : 00011011 00100100 01000010 01001110 01110011 \
00011011 00101000 01000010 

nil
CL-USER> 

のように,全日本語に `1B 24 42` と `1B 28 42` のエスケープシーケンスが 入ってしまって見難い!! 実際には下記のようにエンコードされます.

CL-USER> (let ((*print-base* 16))
           (print (ext:convert-string-to-bytes "日本語文字列" charset:iso-2022-jp)))
#(1B 24 42 46 7C 4B 5C 38 6C 4A 38 3B 7A 4E 73 1B 28 42)

エスケープシーケンスは日本語列の前後に一回ずつなんですがねぇ.今回の describe-string は SHIFT-JIS の文字列のビットパターンの調査用のために作っ たもので,ステートフルなエンコーディングには向いてません,と言い訳して おきましょう.

エンコーディングを指定してファイルを開く

これは簡単です.

(defun show-file-with-encoding (filename &optional (encoding :default))
  (with-open-file (s filename :direction :input :external-format encoding)
    (loop for line = (read-line s nil :eof)
          until (eq line :eof)
          do (write-line line))))

のように,external-format でエンコーディングを指定してやれば読み込み時に CLISP が 内部表現に変換してるので,日本語文字列なども正しく表示できます.

CL-USER> (defun show-file-with-encoding (filename &optional (encoding :default))
            (with-open-file (s filename :direction :input :external-format encoding)
                 (loop for line = (read-line s nil :eof)
                       until (eq line :eof)
                       do (write-line line))))
SHOW-FILE-WITH-ENCODING
CL-USER> (show-file-with-encoding "sample.lisp" charset:utf-8)
(defun list-encodings ()
  (let ((syms nil))
    (do-external-symbols (s (find-package :charset)) (push s syms))
    (setq syms (sort syms (lambda (a b) (string>= (symbol-name a) (symbol-name b)))))
    (dolist (s syms)
      (print s))))

(defun test-encoding (&optional (encoding charset:iso-8859-1))
  "iso-8859-1"
  (labels ((conv (v)
       (ext:convert-string-to-bytes
             (ext:convert-string-from-bytes v encoding)
                   encoding)))
    (loop for i from 0 upto 255 always
      (loop for j from 0 upto 255
                  always
                (loop for x across (vector i j)
                      for y across (conv (vector i j))
                      do (format t "~A <=> ~A~%" (vector i j) (conv (vector i j)))
                      always (= x y))))))

;; (inspect-string "日本語表示するよ。utf-8 って 3 byte なんだ?\\" charset:shift-jis)
(defun inspect-string (string encoding)
  "文字列 string を encoding として 16進数/ビットパターンを表示す
る (iso-2022-jp のようにエスケープシーケンスを使うものは冗長な表示
になる)"
  (flet ((char-to-codes (c)
     (coerce (ext:convert-string-to-bytes (make-string 1 :initial-element c) encoding) 'list)))
    (write-string
     (with-output-to-string (s)
       (loop for c across string
             for bytes = (char-to-codes c)
             do (format s "~2A : ~{~2,'0X ~}: ~{~8,'0b ~}~&" c bytes bytes))))
    nil))
NIL
CL-USER> 

が,ファイルのエンコーディングを判定する機能が標準で備わっていないため 日本語の文字コードが混在している環境ではちょっと不便です.Perl の Encode::Guess モジュール相当の機能が欲しいところです.

convert-string-from-bytes のパラメータで :guess キーワードを追加して判 定できるようにする,といった拡張ですかね.

posted: 2006/03/09 23:35 | permanent link to this entry | Tags: LISP

(top)  (memo)  (rss)