バイト列→文字列変換ライブラリ(sbcl)

前回の知見(文字コード変換関数をC++で用意する云々)を反映して、FFIを利用した、バイト列を文字列に変換するsbclのライブラリを作成した。
unsafe-conv(0.0.1)


対応している文字コードは、UTF-8Shift_JISEUC-JPの三つ。UTF-8以外のC++の変換関数については、この記事の末尾を参照(UTF-8に関しては前回を参照)

完成度はともかく、速度だけならsbclの対応する関数(sb-ext:octets-to-string)より一桁程度速い。

;; 使える関数は一つだけ
> (unsafe-conv:octets-to-string (sb-ext:string-to-octets "ライブラリ"))
--> "ライブラリ"

> (unsafe-conv:octets-to-string 
    (sb-ext:string-to-octets "ライブラリ" :external-format :euc-jp)
    :external-format :euc-jp)
--> "ライブラリ"

名前の通りあまり安全ではないので、一応注意点のようなものを(上記リンク先ファイルの)READMEに書いておきました。
もし使おうとする人がいたら、一読しておくようお願いします。

バイト列→ユニコード変換C++関数生成関数

バイト列から文字列に変換するC++の関数は、以下の関数群を用いて生成した。

;; バイト列とユニコードの対応テーブルを作成
(defun generate-convert-table (external-format)
  (loop FOR i FROM 0 BELOW char-code-limit
        FOR os = (ignore-errors 
                  (string-to-octets (string (code-char i)) 
                                    :external-format external-format))
        WHEN os
        COLLECT (list (coerce os 'list) i)))

;;; 以下二つの関数は、trie作成関連
(defun set-trie-value (trie key val)
  (if (null key)
      (setf (gethash :value trie) val)
    (set-trie-value (setf (gethash (car key) trie) 
                          (gethash (car key) trie (make-hash-table)))
                    (cdr key)
                    val)))

(defun make-trie (convert-table)
  (let ((trie (make-hash-table)))
    (loop FOR (octets char-code) IN convert-table DO
      (set-trie-value trie octets char-code))
    trie))

;;; 以下二つはcppコード出力関連
(defun print-switch-statement (out trie level)
  (if (gethash :value trie)
      (format out " return ~D;~%" (gethash :value trie))
    (progn 
      (format out "~&~V@Tswitch(*s++){~%" (* level 4))
      (maphash
       (lambda (code subtrie)
         (format out "~&~V@Tcase ~D:" (* level 4) code)
         (print-switch-statement out subtrie (1+ level))
         (unless (gethash :value subtrie)
           (format out "~&~V@Tbreak;~%" (* (1+ level) 4))))
       trie)
      (format out "~&~V@T}~%" (* level 4)))))

(defun print-convert-c++-function (out external-format)
  (let ((trie (make-trie (generate-convert-table external-format))))
    (format out "~&unsigned ~(~A~)_to_ucs(const unsigned char*& s) {~%" external-format)
    (print-switch-statement out trie 1)
    (format out "~&}~%")))

;;;; 使用例
(with-open-file (out "sjis_to_ucs.h"  :direction :output)
  (print-convert-c++-function out :sjis))