根据这篇文字推荐的内容进行粗略阅读,目的是掌握Lua语言整体上设计的思路。对于细节部分暂且忽略。
大家可以参看这个blog,也有一些关于lua代码阅读的文字。
阅读之前的准备也很简单。我使用VC2008+VisualAssitant作为代码阅读器。
关于lua架构设计,有一篇论文是必读的http://www.codingnow.com/2000/download/The%20Implementation%20of%20Lua5.0.pdf,另外非常有帮助的是Programming In Lua(简称PIL)这个文档,你可以从这里下载。
lua源代码可以在这里在线阅读http://www.lua.org/source/5.1/,另外还有一个索引页面http://www.lua.org/source/5.1/idx.html也是非常有帮助。
我的这几篇blog仅作为一个学习的记录,所以会比较潦草一些,大家见谅。另外我的代码阅读是从lua解释器开始,如果有时间再阅读luac这个项目。
第一部分,lmathlib.c的阅读。
这段代码与Lua核心实际上是分离的。详细点说就是Math以及String模块的实现不是语言核心组件,而是通过C语言扩展模块的形式提供支持的。Lua的优点其实就在此,它的核心很精巧,扩展机制非常强大。
———————————
lmathlib.c
———————————
#undef PI
#define PI (3.14159265358979323846)
#define RADIANS_PER_DEGREE (PI/180.0)
static const luaL_Reg mathlib[] = {
{"abs", math_abs},
{"acos", math_acos},
{"asin", math_asin},
{"atan2", math_atan2},
{"atan", math_atan},
{"ceil", math_ceil},
{"cosh", math_cosh},
{"cos", math_cos},
{"deg", math_deg},
{"exp", math_exp},
{"floor", math_floor},
{"fmod", math_fmod},
{"frexp", math_frexp},
{"ldexp", math_ldexp},
{"log10", math_log10},
{"log", math_log},
{"max", math_max},
{"min", math_min},
{"modf", math_modf},
{"pow", math_pow},
{"rad", math_rad},
{"random", math_random},
{"randomseed", math_randomseed},
{"sinh", math_sinh},
{"sin", math_sin},
{"sqrt", math_sqrt},
{"tanh", math_tanh},
{"tan", math_tan},
{NULL, NULL}
};
/*
** Open math library
*/
LUALIB_API int luaopen_math (lua_State *L) {
luaL_register(L, LUA_MATHLIBNAME, mathlib);
lua_pushnumber(L, PI);
lua_setfield(L, -2, "pi");
lua_pushnumber(L, HUGE_VAL);
lua_setfield(L, -2, "huge");
return 1;
}
mathlib定义了一个名字(字符串)到函数的映射数组,然后在函数luaopen_math中,通过luaL_register注册这个映射数组,这样Lua就可以通过函数名来调用扩展函数。这实际上也是其他C语言扩展Lua最常用的步骤。
另外定义了两个常数pi和huge。
真正工作的函数类似下面这样:
static int math_ceil (lua_State *L) {
lua_pushnumber(L, ceil(luaL_checknumber(L, 1)));
return 1;
}
static int math_floor (lua_State *L) {
lua_pushnumber(L, floor(luaL_checknumber(L, 1)));
return 1;
}
static int math_fmod (lua_State *L) {
lua_pushnumber(L, fmod(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
return 1;
}
基本上都是通过标准的ansi C库函数实现了math模块的功能。比较值得一提的是luaL_checknumber(L, 1),在PIL(programming in lua一书,以后略写为PIL)是这样介绍的:
辅助库中的luaL_checknumber函数可以检查给定的参数是否为数值类型:如果该参数不是数值类型,则抛出一个错误信息,否则,返回作为参数的数值。
关于lua_pushnumber,在PIL中的介绍如下:
函数lua_pushnumber可以将数值型的值压栈,该类型可以用C语言中的双精度类型double表示;函数lua_pushboolean可以将布尔型的值压栈,该类型可以用C语言中的整数类型int来表示;函数lua_pushlstring可以将含有任意字符的字符串压栈,该类型可以用C语言中的字符指针char *来表示;而函数lua_pushstring可以将C风格(以“\0”结束)的字符串压栈:
void lua_pushnil(lua_State * L);
void lua_pushboolean(lua_State * L, int bool);
void lua_pushnumber(lua_State * L, double n);
void lua_pushlstring(lua_State * L, const char * s, size_t length);
void lua_pushstring(lua_State * L, const char * s);
同样也有可将C函数和Userdata值压栈的函数
———————————————-
static int math_min (lua_State *L) {
int n = lua_gettop(L); /* number of arguments */
lua_Number dmin = luaL_checknumber(L, 1);
int i;
for (i=2; i<=n; i++) {
lua_Number d = luaL_checknumber(L, i);
if (d < dmin)
dmin = d;
}
lua_pushnumber(L, dmin);
return 1;
}
取最小值算法:先得到参数个数lua_gettop(L),取出值进行比较,最后返回最小值lua_pushnumber(L, dmin)。
———————————————-
static int math_random (lua_State *L) {
/* the `%’ avoids the (rare) case of r==1, and is needed also because on
some systems (SunOS!) `rand()’ may return a value larger than RAND_MAX */
lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX;
switch (lua_gettop(L)) { /* check number of arguments */
case 0: { /* no arguments */
lua_pushnumber(L, r); /* Number between 0 and 1 */
break;
}
case 1: { /* only upper limit */
int u = luaL_checkint(L, 1);
luaL_argcheck(L, 1<=u, 1, "interval is empty");
lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u’ */
break;
}
case 2: { /* lower and upper limits */
int l = luaL_checkint(L, 1);
int u = luaL_checkint(L, 2);
luaL_argcheck(L, l<=u, 2, "interval is empty");
lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l’ and `u’ */
break;
}
default: return luaL_error(L, "wrong number of arguments");
}
return 1;
}
static int math_randomseed (lua_State *L) {
srand(luaL_checkint(L, 1));
return 0;
}
随机数算法:首先得到一个随机的小数,然后根据条件返回相应的数值。
———————————————-
第二部分,lstrlib的阅读。
———————————————-
static const luaL_Reg strlib[] = {
{"byte", str_byte},
{"char", str_char},
{"dump", str_dump},
{"find", str_find},
{"format", str_format},
{"gfind", gfind_nodef},
{"gmatch", gmatch},
{"gsub", str_gsub},
{"len", str_len},
{"lower", str_lower},
{"match", str_match},
{"rep", str_rep},
{"reverse", str_reverse},
{"sub", str_sub},
{"upper", str_upper},
{NULL, NULL}
};
LUALIB_API int luaopen_string (lua_State *L) {
luaL_register(L, LUA_STRLIBNAME, strlib);
createmetatable(L);
return 1;
}
函数定义方式与lmathlib非常类似,不多介绍。
static int str_len (lua_State *L) {
size_t l;
luaL_checklstring(L, 1, &l);
lua_pushinteger(L, l);
return 1;
}
通过内部辅助函数luaL_checklstring返回字符串长度。
LUALIB_API const char *luaL_checklstring (lua_State *L, int narg, size_t *len) {
const char *s = lua_tolstring(L, narg, len);
if (!s) tag_error(L, narg, LUA_TSTRING);
return s;
}
实际上是调用了lapi.c中的lua_tolstring来得到字符串长度。字符串的结构定义如下:
typedef union TString {
L_Umaxalign dummy; /* ensures maximum alignment for strings */
struct {
CommonHeader;
lu_byte reserved;
unsigned int hash;
size_t len;
} tsv;
} TString;
而字符串本身是通过svalue宏拿到的,这个定义非常有意思
#ifndef cast
#define cast(t, exp) ((t)(exp))
#endif
#define getstr(ts) cast(const char *, (ts) + 1)
#define svalue(o) getstr(rawtsvalue(o))
也就相当于(const char*)(o + 1)。
———————————————-
static ptrdiff_t posrelat (ptrdiff_t pos, size_t len) {
/* relative string position: negative means back from end */
if (pos < 0) pos += (ptrdiff_t)len + 1;
return (pos >= 0) ? pos : 0;
}
static int str_sub (lua_State *L) {
size_t l;
const char *s = luaL_checklstring(L, 1, &l);
ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), l);
// luaL_optinteger(L, 3, -1)将会检查第三个参数,如果为none或者nil,那么就用-1来作为默认值。第一第二个值为必须,posrelate()会将-1这样的负索引转为正向索引。
ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), l);
// Lua中string的索引值也是从1开始
if (start < 1) start = 1;
if (end > (ptrdiff_t)l) end = (ptrdiff_t)l;
// 如果索引开始值小于结束值,那么截取一段字符串,否则返回空字符串。
if (start <= end)
lua_pushlstring(L, s+start-1, end-start+1);
else lua_pushliteral(L, "");
return 1;
}
函数sub在PIL的解释为
调用string.sub(s, i, j)可以截取字符串s中包含第i个字符到第j个字符的子串。在Lua中,字符串的第一个索引为1,当然你也可以使用负索引,负索引从字符串的结尾向前计数:索引-1指向字符串的最后一个字符,-2指向字符串的倒数第二个字符,以此类推。所以,string.sub(s, 1, j)返回一个从字符串s头部开始直到第j个字符的子串,string.sub(s, j, -1)返回一个从字符串s第j个字符开始直到尾部的子串(如果不提供第三个参数,则默认为-1,因此string.sub(s, j, -1)可以简写为string.sub(s, j)的形式),而执行string.sub(s, 2, -2)将返回一个去除字符串s的首字符和尾字符后的子串。
——————————————————–
static int str_reverse (lua_State *L) {
size_t l;
luaL_Buffer b;
const char *s = luaL_checklstring(L, 1, &l);
luaL_buffinit(L, &b);
while (l–) luaL_addchar(&b, s[l]);
luaL_pushresult(&b);
return 1;
}
其中luaL_Buffer定义如下:
typedef struct luaL_Buffer {
char *p; /* current position in buffer */
int lvl; /* number of strings in the stack (level) */
lua_State *L;
char buffer[LUAL_BUFFERSIZE];
} luaL_Buffer;
luaL_addchar宏定义如下:
#define luaL_addchar(B,c) \
((void)((B)->p < ((B)->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \
(*(B)->p++ = (char)(c)))
整理下就是:
static void luaL_addchar2(luaL_Buffer* pBuffer, char c)
{
if (pBuffer->p < (pBuffer->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(pBuffer))
{
*pBuffer->p = c;
*pBuffer->p++;
}
}
当字符串长度超过512的时候,会调用luaL_prepbuffer这个函数,这个函数代码如下,其中emptybuffer会把buffer中的数据压入stack中,然后重置p指针指向buffer的首地址,最后buffer的lvl加1。
而adjuststack这个函数是保证stack中压入的string buffer满足B->lvl – toget + 1 >= LIMIT,如果超过这个界限,就会调用lua_concat函数来对栈内已经压入的字符串做concat操作。这也就是为何我们操作频繁大的字符串会比较慢的原因。
LUALIB_API char *luaL_prepbuffer (luaL_Buffer *B) {
if (emptybuffer(B))
adjuststack(B);
return B->buffer;
}
static int emptybuffer (luaL_Buffer *B) {
size_t l = bufflen(B);
if (l == 0) return 0; /* put nothing on stack */
else {
lua_pushlstring(B->L, B->buffer, l);
B->p = B->buffer;
B->lvl++;
return 1;
}
}
static void adjuststack (luaL_Buffer *B) {
if (B->lvl > 1) {
lua_State *L = B->L;
int toget = 1; /* number of levels to concat */
size_t toplen = lua_strlen(L, -1);
do {
size_t l = lua_strlen(L, -(toget+1));
if (B->lvl – toget + 1 >= LIMIT || toplen > l) {
toplen += l;
toget++;
}
else break;
} while (toget < B->lvl);
lua_concat(L, toget);
B->lvl = B->lvl – toget + 1;
}
}
str_reverse最后会调用luaL_pushresult这个函数,将buffer中的字符串压入lua栈中,然后执行lua_concat操作,当然,只有当lvl大于2的时候,才会真正调用string的concat操作。
LUALIB_API void luaL_pushresult (luaL_Buffer *B) {
emptybuffer(B);
lua_concat(B->L, B->lvl);
B->lvl = 1;
}
关于前面用到的函数,在PIL中如下解释:
使用辅助库中缓冲函数的第一步是声明一个类型为luaL_Buffer的变量,然后调用luaL_buffinit初始化该变量,即初始化缓冲区。初始化之后,缓冲区保留了一份Lua状态L的拷贝,因此当我们调用其他操作该缓冲区的函数时就不再需要传递L参数。宏luaL_putchar将单个字符放入缓冲区。luaL_addlstring缓冲函数,该函数将一个指定长度的字符串放入缓冲区,而另一个类似的缓冲函数luaL_addstring将一个以“\0”结尾的字符串放入缓冲区。最后,luaL_pushresult刷新缓冲区并将最终字符串放到栈顶。
其中luaL_putchar实际上就是luaL_addchar。
————————————————-
static int writer (lua_State *L, const void* b, size_t size, void* B) {
(void)L;
luaL_addlstring((luaL_Buffer*) B, (const char *)b, size);
return 0;
}
static int str_dump (lua_State *L) {
luaL_Buffer b;
luaL_checktype(L, 1, LUA_TFUNCTION);
lua_settop(L, 1);
luaL_buffinit(L,&b);
if (lua_dump(L, writer, &b) != 0)
luaL_error(L, "unable to dump given function");
luaL_pushresult(&b);
return 1;
}
关于dump函数,其实没什么特别的,大家可以看出基本上和reverse类似,初始化一个buffer,然后通过writer函数来使用buffer。
不过大家可以注意一下这种设计方法,其中writer是可配置的,也就是说,如果想把这个string写到别的地方,只要修改writer这个函数就可以了。类似的设计有:
typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz);
typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud);
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
——————————————————-
在string这个模块中除了find,gsub,gmatch这些非常复杂的函数,format算是一个中等复杂度的功能函数了。
我们调试下面这段lua代码来看看。string.format("pi = %.4f, %02d, %s”, 3.1415, 223, "abcde")
第一步取出参数第一部分(luaL_checklstring(L, 1, &sf1)),赋值给strfrmt字符串。
当我们发现了%,就准备读取format格式字符(else{…}部分),通过调试窗口可以看出scanformat返回值为"d, %s"(第一个格式字符运行窗口没来得及截取,见谅),form返回值为%02d,所以当我们使用switch判断(*strfrmt)的时候会进入case ‘d’部分。
addintlen(form);将%02d转换为%02ld。接下来,通过sprintf对buff进行格式化。
然后是通过luaL_addlstring(&b, buff, strlen(buff));将buff字串加入本地的Buffer中。
当字符串过长的时候(长度大于100),Lua会直接将这个参数先放入栈顶,然后放入缓存区。原因可以在PIL的27.2结尾部分看到:
基于上述情况的普遍性,辅助提供了一个特殊函数,用来将位于栈顶的值放入缓冲区:
void luaL_addvalue(luaL_Buffer * B);
static int str_format (lua_State *L) {
int arg = 1;
size_t sfl;
// 返回输入的字符串。
const char *strfrmt = luaL_checklstring(L, arg, &sfl);
// 定义一个结尾标记指针
const char *strfrmt_end = strfrmt+sfl;
luaL_Buffer b;
// 初始化buffer。
luaL_buffinit(L, &b);
//如果没有到结尾,就处理字符串。
while (strfrmt < strfrmt_end) {
// 当前字符不是%,也就是一个正常字符串,把它加入buffer中,然后当前指针++后移。
if (*strfrmt != L_ESC)
luaL_addchar(&b, *strfrmt++);
// 当前指针是%而且下一个也是%,把%字符加入buffer。
else if (*++strfrmt == L_ESC)
luaL_addchar(&b, *strfrmt++); /* %% */
// 当前指针指向format字符,通过scanformat读取。
else { /* format item */
char form[MAX_FORMAT]; /* to store the format (`%…’) */
char buff[MAX_ITEM]; /* to store the formatted item */
arg++;
strfrmt = scanformat(L, strfrmt, form);
switch (*strfrmt++) {
case ‘c’: {
sprintf(buff, form, (int)luaL_checknumber(L, arg));
break;
}
case ‘d’: case ‘i’: {
addintlen(form);
sprintf(buff, form, (LUA_INTFRM_T)luaL_checknumber(L, arg));
break;
}
case ‘o’: case ‘u’: case ‘x’: case ‘X’: {
addintlen(form);
sprintf(buff, form, (unsigned LUA_INTFRM_T)luaL_checknumber(L, arg));
break;
}
case ‘e’: case ‘E’: case ‘f’:
case ‘g’: case ‘G’: {
sprintf(buff, form, (double)luaL_checknumber(L, arg));
break;
}
case ‘q’: {
addquoted(L, &b, arg);
continue; /* skip the ‘addsize’ at the end */
}
case ‘s’: {
size_t l;
const char *s = luaL_checklstring(L, arg, &l);
if (!strchr(form, ‘.’) && l >= 100) {
/* no precision and string is too long to be formatted;
keep original string */
lua_pushvalue(L, arg);
luaL_addvalue(&b);
continue; /* skip the `addsize’ at the end */
}
else {
sprintf(buff, form, s);
break;
}
}
default: { /* also treat cases `pnLlh’ */
return luaL_error(L, "invalid option " LUA_QL("%%%c") " to "
LUA_QL("format"), *(strfrmt – 1));
}
}
// 将buff压入字符串BUFFER中。
luaL_addlstring(&b, buff, strlen(buff));
}
}
//将字符串BUFFER压入堆栈中。
luaL_pushresult(&b);
return 1;
}
—————————————————
关于gsub,gmatch,find这些涉及到正则表达式(lua格式)的函数,就不做解释了,因为其中的逻辑实在是非常难理解,最好的办法就是通过debug方式来跟踪lua的运行轨迹。其中比价值得记忆的c编程技巧就是下面这几个函数的使用,其中有几个我也没听过,但是在K&R上都可以查到。
static int match_class (int c, int cl) {
int res;
switch (tolower(cl)) {
case ‘a’ : res = isalpha(c); break;
case ‘c’ : res = iscntrl(c); break;
case ‘d’ : res = isdigit(c); break;
case ‘l’ : res = islower(c); break;
case ‘p’ : res = ispunct(c); break;
case ‘s’ : res = isspace(c); break;
case ‘u’ : res = isupper(c); break;
case ‘w’ : res = isalnum(c); break;
case ‘x’ : res = isxdigit(c); break;
case ‘z’ : res = (c == 0); break;
default: return (cl == c);
}
return (islower(cl) ? res : !res);}
《“Lua代码阅读(1)”》 有 1 条评论
[…] http://sunxiunan.com/?p=1358 Lua代码阅读(1),可惜烂尾了 […]