Oji --- バイト列の文字境界を識別する
Ojiは文字符号化の方法が分かっているバイト列における「一文字」の範囲を識別するライブラリです。
どうしよう、これ。
いちおう動機
flexi-streamsにASCII範囲外の文字を渡してstream-read-char
すると、正しく一文字を認識してくれない現象を発見しました。が、これは誤りでした。
以下がまず、CL標準のread
でテキストストリームから一文字を取ってきた場合です。ひらがなの「こ」が取得されているのがわかります。
;; 文字列の一文字目「こ」が読まれる CL-USER> (defparameter str "これはペンです") STR CL-USER> (with-input-from-string (in str) (format t "read from stream: ~s~%" (read-char in))) read from stream: #\HIRAGANA_LETTER_KO NIL
次に、flexi-streamsのほうでstream-read-char
した場合です。こちらは、flexi-streamsのin-memory stream (バイナリストリーム) をbivalentなストリーム (バイナリ・テキスト両ストリームとして扱える) に変換した場合に発生します。
;; 文字列の一文字目「こ」ではない文字が読まれる CL-USER> (defparameter octets (flex:string-to-octets str :external-format :utf-8)) OCTETS CL-USER> (flex:with-input-from-sequence (in octets) (format t "read from flexi stream: ~s~%" (trivial-gray-streams:stream-read-char (flex:make-flexi-stream in)))) read from flexi stream: #\LATIN_SMALL_LETTER_A_WITH_TILDE
flexi streamに対してのstream-read-char
では~
が読み取られていますが、これは「こんにちは」をUTF-8で符号化したときの最初のバイトの値です。
;; UTF-8の文字としてではなく、ASCIIバイト列として一番目を見てみる CL-USER> (format t "first byte as a character: ~s~%" (code-char (aref octets 0))) first byte as a character: #\LATIN_SMALL_LETTER_A_WITH_TILDE NIL
と、ここまで書いてて気づいたんですが、実際には、flexi streamを作るときにexternal-format
を指定すれば、ちゃんとデコードしてくれます。ほんといま気づいた……😢
;; なんとexternal-format指定漏れ! CL-USER> (flex:with-input-from-sequence (in octets) (format t "read from flexi stream: ~s~%" (trivial-gray-streams:stream-read-char (flex:make-flexi-stream in :external-format :utf-8)))) read from flexi stream: #\HIRAGANA_LETTER_KO
flexi-streamsは古くからあるライブラリだし、そういう部分は枯れてて当然ですね。
はずかしい……。消えたい……。
使い方
CL-USER> (setf moji (oji:load-bytes (babel:string-to-octets "これはペンです" :encoding :utf-8) :utf-8)) CL-USER> (oji:encoding moji) :utf-8 ;; read-charは未実装 CL-USER> (oji:read-char moji) #\HIRAGANA_LETTER_KO ;; it's not #\LATIN_SMALL_LETTER_A_WITH_TILDE CL-USER> (oji:boundary moji) ((0 . 2) (3 . 5) (6 . 8) (9 . 11) ...)
感想とか今後とか
どうしよう、これ。
当初の目的としてはふたつあって
- flexi-streamsに妙な点があるので直したい (そんな点なかった)
- バイト列から文字境界認識や区点番号へのデコードをすることで、文字符号化方式への理解を深める
- そこからinquisitorのエンコーディング判定部分の改善に繋げたい
だったんですが、そのうち1が潰えてしまい、えええーーー……😩
そうすると、このライブラリ、何に使えるのだろう。とりあえずUTF-8の文字境界認識と区点番号デコードができたので、るんるん気分で「これからinquisitorサポートの文字コードについて実装していきます!!」とか書こうと思ってたのに……。
動揺していますが、まあ遊びで使うことはできそうだし、調べるの楽しかったので、開発がんばろうと思います!