转载:
http://blog.codingnow.com/2008/12/erlang_shell_utf-8.html
发现在 Ubuntu 下用 apt 装的 Erlang (5.6.5) 的 Shell 不支持中文,这让我郁闷了一把。:(
好在 Erlang 是开源的,有源码在手,有何畏惧。晚上,我卯足了劲研究 Erlang 的 source code ,想找到不支持中文显示的原因。
表现是这样的,我的环境是 UTF-8 ,当我输入汉字时,在 Erlang Shell 里立刻被转义为了 \xxx 这样的 8 进制数。在《Erlang 程序设计》的 2.11 / 21 页写道,“这实际上是显示终端的字符集和区域设定有问题”。我改了半天,都没有把汉字鼓捣出来,感觉不是我的设置问题。
但是,在同事的 Windows 机器上的 Erlang Shell 下却是可以正确显示中文的。
我想,读读源码也好,正好可以实际体会一下 Erlang 的代码风格。
--------------------------------------------
在读代码的过程中绕了不少弯路,找到许多貌似会影响输出的地方,改过后都没有效果。
比如在
erts/emulator/beam/erl_printf_term.c
里有一个
IS_CNTRL
的宏,为 LATIN1 的字符集 hard code 了一些东西,有兴趣的同学可以看看,其中也有一些字符转换的工作。
类似的地方就不多说了,稍微浏览过 Erlang 底层的一些 C 代码后,感觉质量还不错,不过在可移植性和 C 语言标准上,没有 Lua 的源代码严谨 。因为 Lua 里就没有为某些字符集特别定制的代码。好吧,我就看了几个小时而已,权当是一个偏见。
最终找到了关键的地方,一共有两处:
一处在
lib/stdlib/src/io_lib.erl
文件里,打开这个文件就可以看到前面有些关于 ISO 8859-1 / Latin-1 的信息。 搜索这个文件,会发现它把 $\240 到 $\377 之间的字符认为是可以打印的。但是对于 UTF-8 编码来说,这不够。把 $\240 改成 $\200 即可。
在 Erlang Shell 里,只有整个整数数组里的数字都是可打印字符,才会显示成 "xxxxx" 的形式,否则就是 [xxx,xxx,xxx,xxx] 这样的。做了这个修改后,就不会在有 UTF-8 的中文串被转换为 [数字] 了。
第二处在 C 代码里。
因为 Erlang 的内部是自己管理的若干进程(非操作系统进程),为了让用户输入和输出可以统一,所以实际上,和用户交互的 IO 是序列化在一个进程里完成的。因此,我们看到的 io:format 的输出,其实是向管理 IO 的进程发了一个消息而已。
为了找到这个最终真正处理输入输出的进程,我查看了 Erlang 的源代码,就是 lib/kernel/src 的部分。最后发现,是 user 模块做最终的汇总,然后交给
user_drv
去处理 IO 。
但是,我们知道 Erlang 自己是不可能完成 IO 操作的,必然会涉及 C 实现的代码,去跟 OS 交互。在
user_drv
里我们发现了一个叫
tty_sl
的东西。这就是 C 实现的终端部分了。
Erlang 和其它语言交互的方式很有趣,是用进程间(不一定是操作系统级的)通讯完成的,用二进制数据流交互。这跟我们游戏服务器的过进程结构很像。不得不说,Erlang 做的非常优美。今天太晚,不展开评论了。
回到主话题上,这个
tty_sl
的实现在
/erts/emulator/drivers/unix/ttsl_drv.c
。读一读这个源文件,大概就能理解 C 模块如何跟 Erlang 交互的了。
关于中文显示的问题,正在于,这个实现中,hard code 写死了当字符大于等于 128 时的处理方法:转换为 \八进制表示。
我简单增加了一个 ISPRINT 宏,替换掉原来的 isprint 调用,让大于等于 128 的字符也返回 true 。重新编译安装后,Erlang 的 Shell 就可以在 Ubuntu 下正确显示中文了。
不过还有一个问题,光标的处理不太正确,比如退格键可以消掉半个汉字。我想,比较简单并健壮的修改方法应该是把这个 tty 服务的内码改成 UCS-2 的,这样比较容易让单个汉字变成原子的。当然,想办法让它正确处理 UTF-8 的内码也行。
留到以后再改吧。