読者です 読者をやめる 読者になる 読者になる

バイト列->文字列(コンディション処理)

common lisp condition sbcl

バイト列を文字列に変換したい時には、sbclではsb-ext:octets-to-stringという関数を使えば良いのだが、これはそのままでは、文字コードの解釈に部分的にでも失敗するとバイト列全体の変換が失敗するという難があった*1


バイト列のデコードに失敗した場合は、コンディションが通知され、下のような会話型デバッガ(?)が起動されるので、そこで手作業で正しい値を指定することは出きるのだが、毎回全ての値を手動で入力するのは現実的ではない。

;debugger invoked on a SB-IMPL::INVALID-UTF8-STARTER-BYTE in thread #<THREAD ;"initial thread" RUNNING {A889621}>:
;  Illegal :UTF-8 character starting at byte position 420.

;Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

;restarts (invokable by number or by possibly-abbreviated name):
;  0: [USE-VALUE] Supply a replacement string designator.  -> 正しい値を指定
;  1: [ABORT    ] Exit debugger, returning to top level.


そこで、いろいろな試行錯誤を経て、次のようなコードで問題が解決することが分かった。

(handler-bind ((sb-impl::octet-decoding-error
		(lambda (c) 
		  (use-value "?" c))))  ; デコードに失敗した箇所には"?"を埋める
  (octets-to-string octets))

これだと、解釈不能なバイト列の部分が、"?"(or 任意の文字列)で埋められるだけで、処理は最後まで継続され、文字列が返される。

それにしても、common lispのconditionは便利そうではあるのだが、ドキュメントが少ないので使い方が分かりにくくて困る。

蛇足

試行錯誤の一環として書いた、怪しいコードを載せておく。

;;; デコード失敗のコンディションを生成している関数を、無理やり上書きする

(defun my-decoding-error (array start end external-format reason pos)
  "?")

(defun my-octets-to-string (octets)
  (with-unlocked-packages (:sb-impl)
    (let ((orig #'sb-impl::decoding-error))
      (setf (symbol-function 'sb-impl::decoding-error) #'my-decoding-error)
      (unwind-protect 
	  (octets-to-string octets)
	(setf (symbol-function 'sb-impl::decoding-error) orig)))))

*1:例えば、複数も文字コードが混在する場合は、デコードに必ず(?)失敗する