Lua源码剖析(lstrlib.c)

0、lstrlib.c模块是Lua字符库string的实现,读该模块源码是为了学习Lua与C的API以及注册方法。




1、类似于math库,Lua用下面的的代码来注册string模块以及里面的接口:


static const luaL_Reg strlib[] = {
 {"byte", str_byte},
 {"char", str_char},
 {"dump", str_dump},
 {"find", str_find},
 {"format", str_format},
 {"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}
};




static void createmetatable (lua_State *L) {
 lua_createtable(L, 0, 1);  /* table to be metatable for strings */
 lua_pushliteral(L, "");  /* dummy string */
 lua_pushvalue(L, -2);  /* copy table */
 lua_setmetatable(L, -2);  /* set table as metatable for strings */
 lua_pop(L, 1);  /* pop dummy string */
 lua_pushvalue(L, -2);  /* get string library */
 lua_setfield(L, -2, "__index");  /* metatable.__index = string */
 lua_pop(L, 1);  /* pop metatable */
}




/*
** Open string library
*/
LUAMOD_API int luaopen_string (lua_State *L) {
 luaL_newlib(L, strlib);
 createmetatable(L);
 return 1;
}


类似于math库的注册方法,用一个结构体strlib中包含了字符串库string中所有的库函数,语句luaL_newlib(L, strlib);创建一个table,并且把结构体中所有的方法注册到这个table中。语句createmetatable(L);应该是通过这个调用,可以在Lua程序中进行以下类似的操作:s = "abc",要把s中的字符转换成大写,除了可以用s = string.upper(s)外,还可以用s = s:upper(s)来操作。
 lua_createtable(L, 0, 1);  /* table to be metatable for strings */
 lua_pushliteral(L, "");  /* dummy string */
 lua_pushvalue(L, -2);  /* copy table */
 lua_setmetatable(L, -2);  /* set table as metatable for strings */
 lua_pop(L, 1);  /* pop dummy string */


上面的操作,相当于为所有的字符串创建一个元表,假设为T = {},而下面的操作,则为这个T设置了元方法__index为字符串库string。这样当容易理解s:upper(s)了,当字符串s获取"upper"值不存在时,它就会去其元表中查找,而其元表存在__index元方法,并且为string,因此就去sting中获取"uppper"的值,因此s:upper(s)等价string.upper(s),只不过后面这种操作方法更直接,稍微快些,值得一提的是,Lua禁止API setmetatable修改这个字符串类型的元表。




2、下面选择sting库中几个典型的接口实现来写分析:


/* translate a relative string position: negative means back from end */
static size_t posrelat (ptrdiff_t pos, size_t len) {
 if (pos >= 0) return (size_t)pos;
 else if (0u - (size_t)pos > len) return 0;
 else return len - ((size_t)-pos) + 1;
}


该函数是供内部函数调用的一个接口。在Lua中,最后一个元素通常索引是-1,倒数第二个元素索引为-2,依次类推(实质在许多脚本语言中都是这样的,比如python)。上面的函数就是把这些索引转换为通常的正数索引,注意由于Lua的索引是从1开始的,因此最后一句len - ((size_t)-pos) + 1返回值加1了,参数len是序列的长度。




static int str_reverse (lua_State *L) {
 size_t l, i;
 luaL_Buffer b;
 const char *s = luaL_checklstring(L, 1, &l);
 char *p = luaL_buffinitsize(L, &b, l);
 for (i = 0; i < l; i++)
p[i] = s[l - i - 1];
 luaL_pushresultsize(&b, l);
 return 1;
}


该函数用来string.reverse()的实现,用来逆转字符串。函数首先实例化了结构体luaL_Buffer,该结构体在lauxlib.h中定义如下:
typedef struct luaL_Buffer {
 char *b;  /* buffer address */
 size_t size;  /* buffer size */
 size_t n;  /* number of characters in buffer */
 lua_State *L;
 char initb[LUAL_BUFFERSIZE];  /* initial buffer */
} luaL_Buffer;
LUAL_BUFFERSIZE是一个宏,在luaconf.h定义大小为BUFSIZ,在Linux平台下,该值为8192。在Lua手册中,描述了该结构体通常的两种使用方式:
(1) 首先定义了一个luaL_Buffer类型变量b;
然后调用luaL_buffinit(L, &b)初始化变量b;
接着调用形式为luaL_add*的函数,向buff中,添加值;
最后调用luaL_pushresult(&b),把buff中字符串放入栈顶。


(2) 如果我们预先知道最后结果字符串的大小,则按下面步骤来操作:
首先定义了一个luaL_Buffer类型变量b;
然后调用luaL_buffinitsize(L, &b, sz),分配空间大小为sz,并且初始化它。
接着把处理后的字符串拷贝到buff中;
最后调用luaL_pushresultsize(&b, sz),这里的sz是拷贝到buff中字符串的大小。
我们可以注意到,到结构体luaL_Buffer变量被初始化后,调用后面的Lua的API函数,都无须传递状态参数L,这是因为在初始化luaL_Buffer类型变量b后,这个变量就会保留一份状态L的副本。自使用辅助库的缓冲机制中,必须注意一个细节。当向缓冲中添加东西时,缓冲会将一个些中间结果放入栈中。因此,不应假设栈顶还是和使用缓冲前一样。此外,虽然可以在使用缓冲的过程中将栈用于其他任务,但这些任务所调用的压入和弹出的次数必须相等。辅助库提供了一个专门的函数来将栈顶的值加入缓冲:void luaL_addstring (luaL_Buffer *B, const char *s);。
接口str_reverse()用的第二种方式来使用luaL_Buffer的。首先定义一个luaL_Buffer类型的变量b,然后调用luaL_checklstring来获取原始字符串的信息,接着调用luaL_buffinitsize来初始化变量b,返回指向buff的指针,然后把处理后的数据放入buff中,最后调用luaL_pushresultsize(),把buff中的数据最终结果,压入栈顶。




接口str_lower(),str_upper(),str_rep(),str_char()都是用类似str_reverse()的方法来实现的。


在接口str_byte()实现中,有一个值得注意的是,在往栈里压入最终结果前,调用了接口luaL_checkstack()检测栈的大小,防止溢出。而前面的接口实现,使用了结构体luaL_Buffer就无须关心栈溢出的情况了。



关于在C函数中,怎样操作从Lua中传入的字符串参数,以及怎样返回字符串结果给lua,可以参照<PIL2th>中的27.2节。



lstrlib.c中其他的代码都是用来实现字符串格式化和模式匹配。下面我们首先来看模式匹配的实现的代码,相对于其他语言,lua的模块匹配的实现非常简洁,但功能仍然非常强大。关于lua模式匹配的函数以及符号的使用,可以参照<PIL2th>中的第20章。


首先我们来看string.find和string.match的实现,从下面代码:
static int str_find (lua_State *L) {
 return str_find_aux(L, 1);
}




static int str_match (lua_State *L) {
 return str_find_aux(L, 0);
}


可以看出,他们都是调用接口str_find_aux来实现的。str_find_aux的代码如下:
static int str_find_aux (lua_State *L, int find) {
 size_t ls, lp;
 const char *s = luaL_checklstring(L, 1, &ls);  //获取原始数据字符串
 const char *p = luaL_checklstring(L, 2, &lp);  //获取模式字符串
 size_t init = posrelat(luaL_optinteger(L, 3, 1), ls);  //获取开始匹配的位置
 if (init < 1) init = 1;
 else if (init > ls + 1) {  /* start after string's end? */
lua_pushnil(L);  /* cannot find anything */
return 1;
 }
 /* explicit request or no special characters? */
 //若调用的是string.find()并且第四
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值