lua词法语法分析
来源:互联网 发布:2016原创歌词网络投稿 编辑:程序博客网 时间:2024/06/17 14:35
lua是我工作中的第一语言,因而工作的大部分时间,都在敲着lua代码。虽然它语法是否简单
好学,但它来做工程的人,都不免要抱怨作者的一些设定理念。 据说作者是一个学院派的人(从他的
sample代码中就能看出这点),很少也不会去考虑做工程的人的需求。 因而,留给我们这些使用lua
的人不少痛苦。
大伙不爽的事情如下:
1. 默认的变量声明是全局的, 局部需要 local 关键字。
原因: 正常的一段代码, local的变量怎么也比全局的多, 如果反过来, 想python一样,
默认是local , global 来定义全局能省下不少要敲的代码量。并且避免不小心敲错一个
单词导致多一个global变量,而又无法察觉。
2. 没有continue....
- -,说不好其实也没啥,毕竟绕一下还是能实现,但作为一门成熟的编程语言,应该
不至于为了省点语法分析代码而不实现continue吧
3. hash 和 array不区分
这应该说是lua的特点, 只要把table用得正确也不会有太大问题。 但对于初学者
每次都必须花时间去了解table.getn之类的语义。 而且在我们项目里,因为很多table
不能区分到底是当array还是hash用,每次求table的大小时都必须一项一项去数。而且这
似乎还成为了编程的惯例。因而,我的设想是如果table里面能多一项来跟着table元素的
个数。将省不少麻烦。
说是这样说,但是如果真正去改虚拟机,也是个不少的工作量。而最麻烦的是,如果项目真的使用
自己改虚拟机,那么将会对以后使用第三方库带来很多麻烦。所以这一直只是个想法而没人去尝试。
不过既然都想到这点,就不能不了解一下lua虚拟机的词法和语法分析过程。 前阵子花的点时间去
阅读这部分源码。在此大家分享一些笔记。
lua源文件中与编译相关的主要有四个lopcodes(字节码),llex(词法分析),lparser(语法分析),lcode(目标代码生成).
lopcodes里定义了指令的格式,寄存器和常数的表示等.这里记录下一些用语的细节,方便下文.相信大家都比较熟悉.
--[[
一个指令由32位的值,对于有三种模式。
iABC,iABx 和 iAsBx, i是6位的操作码,A为8位, BC为9位, Bx 和 sBx为18位。
A参数通常用作为需要赋值的寄存器, sBx一般用于做跳转量。
然后是常量与寄存器的索引。lua通常使用255个寄存器,第n个寄存器一般表示为R(n),
而常量从256开始编号。 常量与寄存器一起成为RK值,RK(x)如果x小于256则为R(x)
否则,为K(x-256),即第x-256+1个常量。RK值经常作为指令的参数。
pc指下一个指令的位置
--]]
----------------------------------------------------------------------------------------------------------------
词法分析
词法分析过程比较简单,主要的处理函数是int llex(LexState *ls, SemInfo *seminfo)
它接受一个LexState结构,通过其中的buff读取字节,返回一个token和将语义信息填充到SemInfo中(字符串和数值)。
要注意的地方有两个。
一是保留字的处理。lua在初始化时调用luaX_init,建立起所有保留字的字符串对象(同时让它永远不回收,调用luaS_fix)。
并且设定其reserved值为该保留字的枚举值。因为lua中相同的字符串只有一份,所以在llex中遇到reserved字段非空的
字符串即为保留字,否则为TK_NAME。
二是多行注释和字符串。即当读取到'--[' 或 '['的时候,要处理长字符串的情况。通过skip_sep 和 read_long_string处理两段‘=’
数量的匹配。
----------------------------------------------------------------------------------------------------------------
语法分析
语法分析是编译过程的重点,其外部接口是Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name)
它从字节流z读取符合, 建议LexState做词法分析, 同时按照语法生成规则展开, 最后调用到lcode里的接口生成各条指令记录到函数体Proto.
最上层的语法规则都比较容易理解, 如 chunk , statment, block等. 执行根据生成规则一层层打开即可.
需要注意的是以下的内容:
----------------------------------------------------------------------------------------------------------------
1.table的构造过程
表构造的处理函数是void constructor (LexState *ls, expdesc *t)
它先生存一条OP_NEWTABLE指令, 然后根据语法, 按','(或';')切分每项, 根据格式调用listfield 或者 recfield
recfield 生成的是 记录项, 也就是 x = y 或者 [x] = y 的形式, 它用分别用expr求出key 和 value 表单式值,
然后生成一条OP_SETTABLE指令.
这里想说的是数组项的处理方式, 它并没有对每一项产生一个OP_SETTABLE指令, 而是缓存起来再一次性处理的.
其实listfield的只是对之listfield里面仅仅是对value 进行expr调用. 然后记录待处理的项数量+1 (cc->tostore++)
在每次遇到项处理前, 都调用closelistfield, closelistfield 发现当待处理的项达到FPF时(默认50个)
生成一个OP_SETLIST指令, 批量对他们赋值.
OP_SETLIST指令的格式为 OP_SETLIST
意外把 寄存器A中的table批量赋值, 数量为B个, 目标值所指寄存器范围是 [C-1]*FPF+1 ~ [C-1]*FPF+B
因而, 以下语句
local a = { 1; 2; 3; [5] = 'a', 4, 5, [7] = 'b', 6, 7, 8}
的编译结果将是
[01] newtable 0 8 2 ; array=8, hash=2
[02] loadk 1 0 ; 1
[03] loadk 2 1 ; 2
[04] loadk 3 2 ; 3
[05] settable 0 259 260 ; 5 "a"
[06] loadk 4 5 ; 4
[07] loadk 5 3 ; 5
[08] settable 0 262 263 ; 7 "b"
好学,但它来做工程的人,都不免要抱怨作者的一些设定理念。 据说作者是一个学院派的人(从他的
sample代码中就能看出这点),很少也不会去考虑做工程的人的需求。 因而,留给我们这些使用lua
的人不少痛苦。
说是这样说,但是如果真正去改虚拟机,也是个不少的工作量。而最麻烦的是,如果项目真的使用
自己改虚拟机,那么将会对以后使用第三方库带来很多麻烦。所以这一直只是个想法而没人去尝试。
不过既然都想到这点,就不能不了解一下lua虚拟机的词法和语法分析过程。 前阵子花的点时间去
阅读这部分源码。在此大家分享一些笔记。
lopcodes里定义了指令的格式,寄存器和常数的表示等.这里记录下一些用语的细节,方便下文.相信大家都比较熟悉.
--[[
一个指令由32位的值,对于有三种模式。
iABC,iABx 和 iAsBx, i是6位的操作码,A为8位, BC为9位, Bx 和 sBx为18位。
A参数通常用作为需要赋值的寄存器, sBx一般用于做跳转量。
然后是常量与寄存器的索引。lua通常使用255个寄存器,第n个寄存器一般表示为R(n),
而常量从256开始编号。 常量与寄存器一起成为RK值,RK(x)如果x小于256则为R(x)
否则,为K(x-256),即第x-256+1个常量。RK值经常作为指令的参数。
pc指下一个指令的位置
--]]
----------------------------------------------------------------------------------------------------------------
词法分析
它接受一个LexState结构,通过其中的buff读取字节,返回一个token和将语义信息填充到SemInfo中(字符串和数值)。
要注意的地方有两个。
并且设定其reserved值为该保留字的枚举值。因为lua中相同的字符串只有一份,所以在llex中遇到reserved字段非空的
字符串即为保留字,否则为TK_NAME。
数量的匹配。
----------------------------------------------------------------------------------------------------------------
语法分析
它从字节流z读取符合, 建议LexState做词法分析, 同时按照语法生成规则展开, 最后调用到lcode里的接口生成各条指令记录到函数体Proto.
需要注意的是以下的内容:
----------------------------------------------------------------------------------------------------------------
1.table的构造过程
表构造的处理函数是void constructor (LexState *ls, expdesc *t)
它先生存一条OP_NEWTABLE指令, 然后根据语法, 按','(或';')切分每项, 根据格式调用listfield 或者 recfield
recfield 生成的是 记录项, 也就是 x = y 或者 [x] = y 的形式, 它用分别用expr求出key 和 value 表单式值,
然后生成一条OP_SETTABLE指令.
这里想说的是数组项的处理方式, 它并没有对每一项产生一个OP_SETTABLE指令, 而是缓存起来再一次性处理的.
其实listfield的只是对之listfield里面仅仅是对value 进行expr调用. 然后记录待处理的项数量+1 (cc->tostore++)
在每次遇到项处理前, 都调用closelistfield, closelistfield 发现当待处理的项达到FPF时(默认50个)
生成一个OP_SETLIST指令, 批量对他们赋值.
OP_SETLIST指令的格式为 OP_SETLIST
意外把 寄存器A中的table批量赋值, 数量为B个, 目标值所指寄存器范围是 [C-1]*FPF+1 ~ [C-1]*FPF+B
因而, 以下语句
local a = { 1; 2; 3; [5] = 'a', 4, 5, [7] = 'b', 6, 7, 8}
的编译结果将是