同じストリームをバイトストリームやキャラクタストリームとして扱いたい - lisp-cookbook-ja/common-lisp GitHub Wiki

ストリームの要素を、characterでも、unsigned-byteでも扱えるようなストリームを、Bivalent Streamと呼びます。

Common LispでBivalent Streamは、 CLiki:flexi-streams を使用することによって実現可能です。

利用例

おはよう𠀋一郎!

というUTF-8のファイルが、/tmp/foo として存在するとします。

(defun utf-8-char-bytes (byte)
  "最初のバイトから一文字のバイト数を算出する"
  (let ((n (- 8 (integer-length (logxor byte #xff)))))
    (case n
      (0 1)
      (1 nil)
      (otherwise n))))

(defun read-utf-8-char-octets (stream)
  "UTF-8の一文字分のバイトを読む"
  (let* ((1stoct (read-byte stream))
         (chars (utf-8-char-bytes 1stoct))
         (ans (make-array chars :element-type '(unsigned-byte 8))))
    (declare ((vector (unsigned-byte 8)) ans))
    (setf (elt ans 0) 1stoct)
    (loop :for idx :from 1 :below chars
          :do (setf (elt ans idx) (read-byte stream)))
    ans))


(with-open-file (in "/tmp/foo" :element-type '(unsigned-byte 8))
  (let ((in (flex:make-flexi-stream in :external-format :utf-8)))
    (princ
     (list (read-utf-8-char-octets in)
           (read-char in)
           (read-utf-8-char-octets in)
           (read-char in)
           (read-utf-8-char-octets in)
           (read-char in)
           (read-utf-8-char-octets in)
           (read-char in)))
    (terpri)
    (file-position in 0)
    (princ
     (list (read-char in)
           (read-utf-8-char-octets in)
           (read-char in)
           (read-utf-8-char-octets in)
           (read-char in)
           (read-utf-8-char-octets in)
           (read-char in)
           (read-utf-8-char-octets in)))
    nil))
;>>  (#(227 129 138) は #(227 130 136) う #(240 160 128 139) 一 #(233 131 142) !)
;>>  (お #(227 129 175) よ #(227 129 134) 𠀋 #(228 184 128) 郎 #(33))
;=>  NIL

処理系ごとの対応状況

SBCL

SBCLでは、標準でBivalent Streamが利用可能です。利用する為には、openに:element-type :defaultを指定します。

(with-open-file (in "/tmp/foo" :element-type :default)
  (princ
   (list (read-utf-8-char-octets in)
         (read-char in)
         (read-utf-8-char-octets in)
         (read-char in)
         (read-utf-8-char-octets in)
         (read-char in)
         (read-utf-8-char-octets in)
         (read-char in)))
  (terpri)
  (file-position in 0)
  (princ
   (list (read-char in)
         (read-utf-8-char-octets in)
         (read-char in)
         (read-utf-8-char-octets in)
         (read-char in)
         (read-utf-8-char-octets in)
         (read-char in)
         (read-utf-8-char-octets in)))
  nil)
;>>  (#(227 129 138) は #(227 130 136) う #(240 160 128 139) 一 #(233 131 142) !)
;>>  (お #(227 129 175) よ #(227 129 134) 𠀋 #(228 184 128) 郎 #(33))
;=>  NIL

Allegro CL

Allegro CLは標準状態で、Bivalent Streamが利用可能になっています。

参考