为 SBCL 添加 GBK 编码支持
Lisp 2006-12-22 00:18:38 阅读451 评论 字号:大中小
有时很羡慕 Perl 程序员:CPAN 太伟大了,各种模块应有尽有。而 Lisp 的配套资源相对要比 Perl, Python, Java 少很多。不过我是 Lisp 程序员,在找不到现成设施的情况下,只好自己动手丰衣足食了。
最近遇到的严重问题是 SBCL,我用的主要 Lisp 平台,不支持 GBK 编码。Lisp 显然比其他语言更懂得字符的含义,对于一个字符流来说,如果没有指定明确的编码,那么默认值可能是 ASCII 或者 UTF-8,但对于含有 GBK 编码字符的流来说,读入操作将会因为无法解码而报错。如果我以字节流的方式读取,那么没有任何问题,但是牺牲了效率:本来我可以一次读一行的,现在只能一个一个字符地读了。
CLISP是所有Lisp里支持编码种类最多的,但我看了CLISP的代码以后发现,这主要是因为CLISP引用了第三方字符集转换C库和GNU C库来得到支持。SBCL的特点是C代码尽可能小,主要语言逻辑用纯Lisp实现,因此每一种编码支持都要用Lisp实现,所以开发者不实现GBK也是情有可原的。SBCL中唯一支持的东方语言编码是主要的几种日文编码,为了实现日文编码和UCS的转换,必须提供完整的编码转换表。我又读了 GNU C 库里关于 GBK 编码的源文件,奇迹般地发现我可以看懂:几张巨大的编码转换表直接以C语言二维数组的形式写在源代码里……于是我从网上随便搜了一张GBK到UCS的编码转换表,就开始自己的移植工作了。
首先,SBCL的编码支持位于 sb-impl 包里。Lisp 的一个明显的好处是修改语言实现时无须重新编译整个代码,在现有的环境下替换或者添加一些函数和变量即可。
下面的程序首先解决了编码转换的接口部分:(enc-cn.lisp)
(in-package :sb-impl)
;;; GBK (declaim (inline ucs-to-gbk gbk-to-ucs mb-len-as-gbk gbk-continuation-byte-p))
(defun ucs-to-gbk (code) (declare (optimize speed (safety 0)) (type fixnum code)) (if (<= code #x7f) code (get-multibyte-mapper *ucs-to-gbk-table* code)))
(defun gbk-to-ucs (code) (declare (optimize speed (safety 0)) (type fixnum code)) (if (<= code #x7f) code (get-multibyte-mapper *gbk-to-ucs-table* code)))
(defun mb-len-as-gbk (code) (declare (optimize speed (safety 0)) (type (unsigned-byte 8) code)) (if (< code #x80) 1 2))
(defun gbk-continuation-byte-p (code) (declare (optimize speed (safety 0)) (type (unsigned-byte 8) code)) (<= #x80 code))
(define-multibyte-encoding :gbk (:gbk :|GBK| :cp936 :|936|) ucs-to-gbk gbk-to-ucs mb-len-as-gbk gbk-continuation-byte-p)
ucs-to-gbk 和 gbk-to-ucs 是编码转换函数,他们非常简单,当需要转换的字符超出 ASCII 范围(0x7f)时,直接到一张巨大的码表里查询即可。mb-len-as-gbk 用于在处理多字节字符集时判定一个字符共有多少字节。对于 GBK 来说,最简单的处理就是当第一个字符在 ASCII 范围之内时程度为1,否则长度为2。gbk-continuation-byte-p用于在处理编码时确认一个字节是位于字符的起始还是中间,我的简单处理是认为当字节不在ASCII范围内时就一定不是一个GBK字符的开始。最后 define-multibyte-encoding 用上述函数构造一个完整的多字节编码环境,它是一个宏,实际上会生成许多东西。
另一个文件就显得无趣多了:(enc-cn-tbl.lisp)
(in-package :sb-impl)
(define-multibyte-mapper *ucs-to-gbk-table* '((#x4e02 #x8140) (#x4e04 #x8141) (#x4e05 #x8142) (#x4e06 #x8143) (#x4e0f #x8144) (#x4e12 #x8145) (#x4e17 #x8146) (#x4e1f #x8147) (#x4e20 #x8148) (#x4e21 #x8149) (#x4e23 #x814a) (#x4e26 #x814b) (#x4e29 #x814c) (#x4e2e #x814d) (#x4e2f #x814e) (#x4e31 #x814f) ... (#xfa13 #xfe45) (#xfa14 #xfe46) (#xfa18 #xfe47) (#xfa1f #xfe48) (#xfa20 #xfe49) (#xfa21 #xfe4a) (#xfa23 #xfe4b) (#xfa24 #xfe4c) (#xfa27 #xfe4d) (#xfa28 #xfe4e) (#xfa29 #xfe4f)))
(define-multibyte-mapper *gbk-to-ucs-table* '((#x8140 #x4e02) (#x8141 #x4e04) (#x8142 #x4e05) (#x8143 #x4e06) (#x8144 #x4e0f) (#x8145 #x4e12) (#x8146 #x4e17) (#x8147 #x4e1f) (#x8148 #x4e20) (#x8149 #x4e21) (#x814a #x4e23) (#x814b #x4e26) ... (#xfe49 #xfa20) (#xfe4a #xfa21) (#xfe4b #xfa23) (#xfe4c #xfa24) (#xfe4d #xfa27) (#xfe4e #xfa28) (#xfe4f #xfa29)))
长达 10000 行的枯燥文本,用于明确定义 GBK 和 UCS 直接的编码关系。我的表格是从 http://www.opensource.apple.com/darwinsource/Current/libiconv-13/libiconv/tests/GBK.TXT 下载的,很容易用 Emacs 把格式转换成 Lisp 可读的形式。
当上述两个文件编辑并加载进 SBCL 以后,SBCL 就具有了理解 GBK 编码的能力了,下面这个小测试可以帮助确定代码的有效性:
准备一个含有 GBK 字符的文本文件 sample.txt,在 UTF-8 环境下试图读出并打印它的第一行:
(with-open-file (s "sample.txt" :direction input :external-format :gbk) (read-line s nil nil nil))
|