我们可以在系统的 Lua 环境中以 Lua 库的形式再嵌入一个修改过的 Lua 解析器,用这个支持汉字变量名的解析器来解析从策划表格中读出来的脚本,生成 bytecode ,然后再在母体中运行它们。正好之前制作了一个多 State 的 共享数据库 用来储存表格数据,完全可以在初始化数据库时使用修改版的 Lua 解析器。
在未修改的 Lua 环境中嵌入另一个版本修改过的 Lua 虚拟机在链接设置上需要特别小心。因为这是两个版本的 Lua 却有相同的 API 。我的做法是先定义一组 C 接口,仅用来编译 Lua 代码:
struct code_state * code_open(void *buffer, size_t sz);
const char * code_load(struct code_state *L, const char * source, size_t *dump_sz);
void code_close(struct code_state *);
这里,struct code_state 其实就是 lua_State 但换个名字以示区分。我在定义接口时,考虑到希望更好的控制内存,在初始化的时候,由外部传入解析中需要用到的内存块。利用 lua 可直接定制 Alloc 的特性(实现一个简单的 bump allocator),让这个独立的 Lua 虚拟机资源使用高效可控。
我把这组 API 实现在一个独立的动态库中,静态链接修改过的 Lua lib ,并不导出任何 Lua 相关的 api 。这样就和母体的 Lua 环境绝缘了。
第二步,实现一个标准的 Lua 扩展库,动态链接前面这组 C 接口,就可以方便的在母体的 Lua 环境中加载支持汉字变量名的 Lua 代码。
如何修改 Lua 源代码支持汉字变量名呢?
Lua 的源代码结构非常清晰,做到这一点相当简单。以 Lua 5.2 为例,语法解析代码在 llex.c 中,但阅读一下就可以发现我们并不需要修改这个文件。Lua 是通过自己定义的 lislalpha lislalnum 两个函数来判断变量名的。
这两个函数定义在 lctype.h 中,我们只需要修改这个文件即可。
下面,我希望让 Lua 认为 UTF-8 中汉字字符也通过 lislalpha 的检查。关于 UTF-8 汉字的编码规则,可以参考之前我写过的一篇 blog 。虽然不太严谨,我直接把 0x80-0xbf 0xe0-0xef 段的字符全部认为是汉字。
根据配置,Lua 定义了两个版本的 lislalpha 。当系统使用标准 ASCII 字符集时,Lua 使用自己优化过的查表版本;否则则调用系统的 isalpha 函数。对于后者,Lua 还检查了下划线,我们只需要追加汉字集的检测即可。
对于前一种情况,可以修改 lctype.c 中 Lua 自己的查表实现,也就是一张表。
在这张表里,Lua 定义了单个字节每个编码的属性,用位域表示的。支持判断一个字符是否是 ALPHA ,是否为数字,或是 16 进制数字,是否可以打印(这在 Lua 标准库中输出字符串有用到)等等。
我们只需要修改这张表就可以了。把第 8 到 B 行,以及 E 行全部修改为 0x01 或是 0x05 (0x05 可以让 Lua 认为汉字是可打印的)。