ccl 是 emacs 中用来转换文件编码的语言。emacs 中内嵌了 ccl 的解释器。 ccl 的代码实际上是一个整数数组,大概类似这样:
;; |----------------- integer (28-bit) ------------------| ;; |------------ 20-bit ------------|- 3-bit --|- 5-bit -| ;; |------------- DATA -------------|-- REG ---|-- OP ---| ;; If REG2 is specified, embed a code in the following format. ;; |------- 17-bit ------|- 3-bit --|- 3-bit --|- 5-bit -| ;; |-------- DATA -------|-- REG2 --|-- REG ---|-- OP ---|
直接写 ccl 代码应该是不可能的事情,emacs 提供了更高级的 ccl 语言,并把它编译成 ccl 解释器能使用的代码。程序的语法应该是比较简单的,但是相关的文档很少,函数 define-ccl-program 中给出了 ccl 的 BNF 语法描述,但是对一些函数功能和概念的说明还是太少。
http://www.xemacs.org/Documentation/21.5/html/lispref_64.html 中给出了一个相对比较全面的说明,而且还给出一个例子,但是,emacs 22 上使用的ccl 应该有进行一定的扩展,比如 read-multibyte-character、 write-multibyte-character、lookup-integer 和 translate-character 在这里没有给出,但是这是很有用的命令。我在大概(实在是有的看不懂:))看了几遍 utf-8, utf-16, code-pages 的源码后,结合一些例子对这几个函数有了一定的了解。
首先,一个 ccl 程序是用 define-ccl-program 来定义的。它的语法形式是:
(BUFFER_MAGNIFICATION CCL_MAIN_CODE [ CCL_EOF_CODE ])
其中 BUFFER_MAGNIFICATION 是输出缓冲和输入缓冲大小的比值。如果设置为 0 就不能用 read 和 write 命令了。
测试一个 ccl 程序可以用 ccl-execute-on-string 函数。比如,写一个最简单的 ccl 程序:
(define-ccl-program ccl-ywb-test
'(2
((write #x41))))
(ccl-execute-on-string ccl-ywb-test
(make-vector 9 nil)
"")
可以看到这个程序确实产生了一个输出 "A"(即 0x41 对应的 ASCII 字符)。
ccl 的运算符、read、write 和循环控制命令在 define-ccl-program 文档和前面给出的文档中都有很多说明,也很容易明白。只有 read-multibyte-character、 write-multibyte-character、lookup-integer 和 translate-character 这四个函数我觉得是比较难懂的。
下面我用一些例子说明。第一个是解码 unicode 的一个例子。"中"字的 unicode 是 0x4e2d,用这个程序可以把这个 unicode 转换成 emacs 的字符:
(define-ccl-program ccl-ywb-test
`(2
((r0 = #x4e2d)
(lookup-integer utf-subst-table-for-decode r0 r1)
(write-multibyte-character r0 r1))))
(ccl-execute-on-string ccl-ywb-test
(make-vector 9 nil)
"")
它是怎样得到这个汉字的呢?首先看看文档中给出的说明:
(lookup-integer SYMBOL REG(integer))
;; SYMBOL refers to a table defined by
;; `define-translation-hash-table'.
utf-subst-table-for-decode 是一个 symbol,它有 translation-hash-table 这个属性:
(get 'utf-subst-table-for-decode 'translation-hash-table)
如果用 #x4e2d 来获取散列表中的值会得到什么呢:
(char-to-string (gethash #x4e2d (get 'utf-subst-table-for-decode 'translation-hash-table)))
是不是得到一个 "中" 字。所以这个 translation-hash-table 提供了一个从 unicode 到 emacs 内部字符转换的表格。
那 lookup-integer 具体有怎样的作用呢?我们先看看 write-multibyte-character 的文档:
;; Write a multibyte representation of a character whose ;; charset ID is REG_0 and code point is REG_1. If the ;; dimension of the charset is two, REG_1 should be ((CODE0 << ;; 7) | CODE1), where CODE0 is the first code point and CODE1 ;; is the second code point of the character. | (write-multibyte-character REG_0 REG_1)
所以用了 lookup-integer 后,r0 中是 charset id,r1 中是 code point 合成的一个数。关于 charset id 和 code point,我做一个简单的说明吧。首先 emacs 把字符分成多个 charset,每个 charset 分配了一个 charset id。一个 charset 中最多只能有 9025(即 95*95) 个字符。一个 charset 如果维数 (charset-dimension)为 2,就能容纳 9025 个字符,每个字符对应一个 code point,这个 code point 由两个字节构成,每个字节的范围都从 0x21-0x7f(刚好 95 个)。可以用 list-charset-chars 命令来查看各个 charset 中的字符。由于字符范围是在 0x7f 以下,所以用对一个 code point 的两个字符进行
((code0 << 7) | code)这样的移位是可以用 (code >> 7) 和 (code & 255) 进行还原的。之所以要进行这样的移位操作,我估计在 emacs 内部可以使用这样得到的数作下标来在 char-table 中查找字符。
总的一句话,lookup-integer 的作用是从 translation-hash-table 中查找出一个整数对应的字符。
如果明白了 lookup-integer,其它函数也就很容易明白了。
(define-ccl-program ccl-ywb-test
`(2
(
(read-multibyte-character r0 r1)
(lookup-character utf-subst-table-for-encode r0 r1)
(write (r0 >> 8))
(write (r0 & 255))
)))
(ccl-execute-on-string ccl-ywb-test
(make-vector 9 nil)
"中")
是不是得到 "N-"(即 #x4e #x2d 两个字节)这个字符串?所以 read-multibyte-character 和 write-multibyte-character 刚好对应,它是从 emacs 的输入中得到一个字符的 charset id 和 code point,而 lookup-character 与 lookup-character 作用刚好相反,它是把 emacs 内部的一个字符根据 translation-hash-table 转换成一个整数。
translate-character 的功能是用 translation-table 把 emacs 内部表示的一个字符转换成另一个字符。比如,cp437(在 mule/code-pages.el 中定义)使用了一个转换表 decode-cp437。在这个表中可以把 0-255 之间的字符转换成 unicode 字符。我们用这个做一个测试。首先在 *scratch* 中用 C-x RET f cp437 确保导入这个编码系统,在 *scratch* 用这个可以显示表中的内容:
(let ((table (get 'decode-cp437 'translation-table))) (dotimes (i 255) (insert (format "%d => %c\n" i (aref table i)))))
比如表中有一个从字符 220 转换成 unicode #x2584 的字符。
(define-ccl-program ccl-ywb-test
`(4
((r0 = 128)
(r1 = 220)
(translate-character decode-cp437 r0 r1)
(write-multibyte-character r0 r1))))
(ccl-execute-on-string ccl-ywb-test
(make-vector 9 nil)
"")
是不是正确转换了?所以 translate-character 是输入的是一个字符的 charset id 和 code point,然后这两个寄存器会转换成另一个字符的 charset id 和 code point。