学习Lua源代码的一些经验和参考资料推荐

断断续续在博客更新Lua5.1.4分析的一些文章,当前写的还是很杂乱。前面大体分析了一些数据结构(通用数据结构,表),一些流程的处理(赋值,逻辑跳转),还有函数相关的代码。后面还有不少没有分析到,目测还有表相关的操作,GC,Lua调试器等等,内容还是不少。

今天暂时打住,记录一下我看Lua代码这段历程来用到的一些知识点,经验和参考资料。

起初决定看Lua代码的理由很简单。每个对代码有点喜欢的人,都会觉得能创造一门语言是很酷而且充满神秘感的事情,尤其对我这样非科班出身的人。龙书很早就买了,一直搁着,上面布满了灰尘。中间也接触过一些编译类的书籍,比如编译原理与实践就实现了一个Toy级别的脚本语言,虽然五脏俱全,但是毕竟离正经产品级的代码还是有距离。

Lua也是一早就听说过,但是一直没有太多了解。直到2011年换工作之后,使用C++

Lua5.1.4代码分析(二十)-函数的返回参数

Lua中,函数的返回参数数量可能会随着赋值表达式左边的情况而进行调整。

比如,同样的函数f(),本来返回两个参数,而如果是表达式A = f(),则第二个返回参数将被抛弃。同样的,如果是表达式A,B,C =

简明Lemon核心代码分析二–向前看符号集的生成

所谓的向前看符号,就是当某一个项目的点号已经在最右边时,当下一个符号是什么符号时,可以使用该产生式进行归约操作。

比如,有一个项目是T→T*F·,它的向前看符号为=号,那么就意味着,如果当前栈的栈顶的符号为T*F,而下一个输入符号为=号时,可以使用产生式T→T*F进行归约操作,也就是将栈顶的T*F符号弹出,压入符号T=。

如果说,前面的buildshifts函数确定了不同的状态之间,经过哪些符号可以到达,那么向前看符号就决定了何时可以进行归约。要计算向前看符号,过程还是比较复杂,需要好几个辅助变量的计算。

首先要计算的是所有符号的first集合。它的算法,简单描述如下:

简明Lemon核心代码分析之一–LR(0)项目的生成

上一篇提到了Lemon这个项目,对于一个只有4000多行代码量的项目,用了一本400页的书来分析它的实现。我觉得应该把书读薄一点,所以我尝试着把这里我认为最核心的部分做一个我自己的诠释。

Lemon是一个LALR的语法分析器,有它自己自定义的语法规则,分析语法文件之后再根据模板文件输出C代码。这里涉及到几个步骤:词法分析,LALR语法分析,生成C代码文件。词法分析没有太多可说,就是根据语法规则定义状态机,提取词法的token,逐个进行分析。这个过程完毕之后,将产生相应的产生式,符号,产生式的左边符号,右边符号,开始符号等等这些都是在这一步完毕之后可以知道的。这一步不做太多的分析,因为相对而言还是比较简单的。而最后一步,也不做分析。重点在第二步,如何根据LALR算法得到动作表。

在我看来,核心包括两步:LR(0)项目的生成,以及每个LR(0)项目的向前看符号的计算。

首先看如何生成LR(0)项目。简单的说,是从开始符号出发,逐个遍历以它为产生式左边符号的产生式,逐个生成LR(0)项目。

LEMON语法分析生成器 书评

前年底开始阅读Lua源码,中间发现编译基础不行,于是折回去看龙书之类的编译书记.前面看的还能明白点儿,到了LALR部分开始卡壳,于是找来这本书看,以Lemon这个仅有几千行代码量的LALR分析器来讲解一个LALR分析器的完整实现,需要补充一下背景知识的是,这并不是一个实验性质的项目,著名的开源项目Sqlite就以它作为sql解析生成器,当然Lemon的作者也是sqlite之父本人.

有了之前生吞编译的一些理论基础,大概花了两周的时间,把该书的绝大部分看完,除了最后一部分根据模板生成相应的代码部分没有细看,因为对于要了解LALR算法的实现,到生成Action和GO表部分,已然足够了.

写代码分析类的书籍,不是一个容易的事情,因为在分析的同时,也需要将相应的背景知识逐个交代.本书这点做的不够好,当然这不是他的责任,读者需要先把编译前端的理论知识补一补,同时随手背一本做参考.

书中的讲解,基本做到了逐行解释,甚至于有些罗嗦,比如插入符号的hash算法也要解释一下.实际上,做为读者而言,应该也要学会看书时抓住重点,对于类似这样的内容,完全可以一目十行–毕竟你真正的目的是要快速了解这个算法,而不是局限在一些代码上的细节.

本书几年前我就在书店看过,时隔多年之后终于买了一本,打开一看发现还是2006年的第一次印刷版本,可见销量之惨淡.这种讲解看似用不上的理论书籍,在中国卖的不好也是可以预料的,不过难免让人唏嘘.

其实我的本职工作,并不是语言编译等相关的,要看懂Lua源码,也着实不必深入到LALR这一层才足够,Lua本身用的是最简单的递归下降分析法,只不过,理解编译这样可以用语言生成语言的技术,对我而言一直以来都很神秘而且觉得能做到了是件很酷的事情.如果有类似体验的同学,强烈建议从Lua和Lemon这种短小精悍又五脏俱全非玩具项目的小项目入手.

给本书打4星半,内容无问题作者也确实扎实的深入探索了一遍Lemon的实现,奈何不是什么人都是侯捷能把知识描述阐述的清晰易懂.

Lua5.1.4代码分析(十九)-Lua协程的实现

协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有API给使用者来暂停和继续执行,而线程由操作系统内核控制;另外,协程也更加轻量级。这样,在遇到某些可能阻塞的操作时,可以使用暂停协程让出CPU;而当条件满足时,可以继续执行这个协程。目前在网络服务器领域,使用Lua协程最好的范例就是ngx_lua了,我自己的项目qnode也是借助Lua协程的概念:每一个qnode中的微进程底层对应一个Lua协程,这样底层的异步操作可以在使用者使用同步的方式写出来。Coool。

来看看Lua协程内部是如何实现的。

本质上,每个Lua协程其实也是对应一个LuaState指针,所以其实它内部也是一个完整的Lua虚拟机—有完整的Lua堆栈结构,函数调用栈等等等等,绝大部分之前对Lua虚拟机的分析都可以直接套用到Lua协程中。于是,由Lua虚拟机管理着这些隶属于它的协程,当需要暂停当前运行协程的时候,就保存它的运行环境,切换到别的协程继续执行。很简单的实现。

来看看相关的API。

1)

内存池及其他

最近在看nginx的代码,看了一下nginx内存池和字符串相关的代码。很简单的代码,就不分析啥了。

对类tcmalloc之类的内存分配库有一定了解的人,都知道其实这些库内部也都会做缓存,不会每次分配就找系统要每次释放就返回系统,因此我一直质疑有没有必要在应用层自己再做一次类似内存池这样的缓存。

回到nginx中,它的内存池算法及其简单,使用时是绑定在比如连接相关的结构体上,一个连接到来分配一个内存池,之后这个连接相关的内存分配操作都在这个内存池中进行,而在连接关闭之后与之相关的内存池就随之关闭。换言之,nginx试图通过内存池这个概念,将内存管理的粒度做的更小,比系统级的更小,这样也更易于管理。而nginx的字符串结构体,仅有一个指针指向字符串和一个保存字符串长度的整型变量,这个字符串在分配之后不可变,那么要分配可变字符串呢,首先使用内存池来分配内存,然后使用类sprintf的函数预先格式化好字符串,随后的事情就交由这个结构体管理。

说了这么多,我想说的是,这几个结构体的设计,将它们的功能尽可能的最小化,正交化:内存池只管内存,与特定的数据结构绑定,生死与它一起;字符串只关心自己的内容,一旦分配不可改变,也不关心内存分配问题。

而对比起来,我自己做的qnode项目,字符串相关的操作就做了以上的所有事情:内存分配,字符串内容管理,支持可变的API,等等等等。太多太多了。

以上,可以解释为什么需要在应用层设计一个内存池,和如何设计数据结构及相关API的好范例。

——————

Lua5.1.4代码分析(十八)-pcall的实现

Lua支持两种形式的函数调用,一种对调用过程的堆栈进行保护,即使中间过程出错,也不至于让进程退出,也就是pcall,一般在使用C调用Lua写的脚本函数时,都采用pcall方式。

对比起一般的函数调用方式,pcall多做了这些事情:对函数调用前的Lua堆栈进行保护在调用完毕之后恢复,支持传入出错时的函数在调用出错时调用。

来依次看这个过程。

首先看入口函数lua_pcall:

LUA_API int lua_pcall (lua_State *L, int nargs, int

类Erlang项目qnode

我在2010年开始接触Erlang,后来2011年开始抄Erlang实现的Riak项目来学习Erlang使用。在这之前,我基本上都在使用C++来编写服务器代码,大部分的C系服务器中,不管是reactor还是proactor模型,在处理非阻塞I/O问题时,都会采用注册回调函数的方式来处理异步I/O,本质上回调函数就是个函数指针,但是经常要带上一些参数的话,复杂一点的可能就会采用C++模板。当逻辑复杂起来时,这样写出来的代码是很难看的,对阅读者而言难,在调试问题时对调试者而言也是个问题。

Erlang采用了另一种方式解决这样的问题。在Erlang中可以创建所谓的“微进程”,当由于I/O等原因可能造成阻塞时,Erlang

Lua检查函数的覆盖问题

Lua里面可以实现类面向对象,这个在后面会分析到。前几天项目里面遇到的一个问题,在不同模块中的同名函数,由于都是挂在某个类下面,会出现后面加载的模块函数覆盖了前面的函数。简单的例子就是这样的:

首先定义一个基类obj,里面有一个名为test的函数:

module("obj", package.seeall)

function test()
  print("in

Pages: Prev 1 2 3 4 5 6 7 8 9 Next