Lua源码剖析(lstrlib.c)
来源:互联网 发布:卖脐橙淘宝店名字 编辑:程序博客网 时间:2024/05/18 02:57
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()并且第四个参数为true,表明忽略模式中的特殊字符,只做简单的原始匹配
//或者模式中,不存在特殊字符,也是做简单的原始匹配,这些特殊字符,包括"^$*+?.([%-",它是用
//宏SPECIALS来定义的,利用内部接口nospecials来判断模式中是否有特殊字符
if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) {
/* do a plain search */
const char *s2 = lmemfind(s + init - 1, ls - init + 1, p, lp);
if (s2) {
//返回匹配到的开始位置和结束位置
lua_pushinteger(L, s2 - s + 1);
lua_pushinteger(L, s2 - s + lp);
return 2;
}
}
else {
/*结构体MatchState用来保存当前匹配的情况,在用递归方法解决某个问题时,引入这样一个结构体,
函数调用之间通过这个结构体来传递信息是非常方便的*/
//这里的结构体MatchState定义如下:
/**************************************/
typedef struct MatchState {
const char *src_init; /* init of source string */
const char *src_end; /* end ('\0') of source string */
const char *p_end; /* end ('\0') of pattern */
lua_State *L;
int level; /* total number of captures (finished or unfinished) */
struct {
const char *init;
ptrdiff_t len;
} capture[LUA_MAXCAPTURES];
} MatchState;
/**************************************/
MatchState ms;
const char *s1 = s + init - 1; //目标字符串开始匹配的位置
int anchor = (*p == '^'); //用来标识是否只会匹配目标字符串的开头部分,准确的讲是指定位置算为开头位置
if (anchor) {
p++; lp--; /* skip anchor character */
}
//初始化状态结构体
ms.L = L;
ms.src_init = s;
ms.src_end = s + ls;
ms.p_end = p + lp;
do {
const char *res;
ms.level = 0;
if ((res=match(&ms, s1, p)) != NULL) {
if (find) {
lua_pushinteger(L, s1 - s + 1); /* start */
lua_pushinteger(L, res - s); /* end */
return push_captures(&ms, NULL, 0) + 2; //同时返回需要捕获到的内容
}
else
return push_captures(&ms, s1, res);
}
} while (s1++ < ms.src_end && !anchor); //从目标字符串下一个位置开始匹配,模式开始位置有"^"标识,表明只匹配开始位置,后面的位置都不用匹配了
}
lua_pushnil(L); /* not found */
return 1;
}
上面接口用nospecials(),用来检测模式字符串中,是否有特殊字符,代码如下:
/* check whether pattern has no special characters */
#define SPECIALS"^$*+?.([%-"
static int nospecials (const char *p, size_t l) {
size_t upto = 0;
do {
//查找p+upto开始的字符串中,是否存在SPECIALS中的字符,并返回第一次出现的位置
if (strpbrk(p + upto, SPECIALS))
return 0; /* pattern has a special character */
upto += strlen(p + upto) + 1; /* may have more after \0 */
} while (upto <= l);
return 1; /* no special chars found */
}
上面接口用lmemfind()来做简单的字符匹配,代码如下:
static const char *lmemfind (const char *s1, size_t l1,
const char *s2, size_t l2) {
if (l2 == 0) return s1; /* empty strings are everywhere */
else if (l2 > l1) return NULL; /* avoids a negative `l1' */
else {
const char *init; /* to search for a `*s2' inside `s1' */
l2--; /* 1st char will be checked by `memchr' *///即下面的while循环判断
l1 = l1-l2; /* `s2' cannot be found after that */
//memchr返回值为*s2在由s1指向的内存中第一次出现的位置,查找过程中是按字节比对的,只比较l1个字节
while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) {
init++; /* 1st char is already checked */
if (memcmp(init, s2+1, l2) == 0)
return init-1;
else { /* correct `l1' and `s1' to try again */
l1 -= init-s1; //前面比较的位置,现在就可以忽略了
s1 = init; //挪到下一个位置开始比对
}
}
return NULL; /* not found */
}
}
在模式匹配中,主要用到接口match,该接口使用了goto,用来优化尾递归,这样做代码看起来逻辑可能更清楚,得到的启发是,没有什么是绝对不能使用的,下面我们来看该接口的实现:
static const char *match (MatchState *ms, const char *s, const char *p) {
init: /* using goto's to optimize tail recursion */
if (p == ms->p_end) /* end of pattern? */
return s; /* match succeeded */ //表明匹配类似""这样的空字符串,返回的指针指向目标字符串开始位置,这里也是结束递归的位置
switch (*p) {
case '(': { /* start capture */
if (*(p+1) == ')') /* position capture? */ //CAP_POSITION用来针对模式中"()"的捕获,从这里可以看到,是目标字符串匹配当前位置s,作为捕获的起始位置,并用这个特殊标识CAP_POSITION。这种捕获也是占一个位置的。
return start_capture(ms, s, p+2, CAP_POSITION);
else
return start_capture(ms, s, p+1, CAP_UNFINISHED); //标识CAP_UNFINISHED用来当前捕获还正在匹配
}
case ')': { /* end capture */
return end_capture(ms, s, p+1);
}
case '$': {
if ((p+1) == ms->p_end) /* is the `$' the last char in pattern? */
//若"$"是模式中最后一个字符,则模块只会匹配目标字符串的结尾部分
return (s == ms->src_end) ? s : NULL; /* check end of string */
else goto dflt;
}
//L_ESC是lua中模式匹配中的转义字符,定义为#define L_ESC'%'
case L_ESC: { /* escaped sequences not in the format class[*+?-]? */
switch (*(p+1)) {
//模式是中%b,用于匹配成对的字符,例如%b()可匹配以"("开始,并以")"结束的子串
case 'b': { /* balanced string? */
s = matchbalance(ms, s, p+2); //返回匹配成对字符串在目标字符串中的下一个位置
if (s == NULL) return NULL;
p+=4; goto init; /* else return match(ms, s, p+4); */
}
//%f[set]要求前一个字符不属于集合set,并且当前字符属于集合set。对于第一个字符,则前一个
//字符设定为"\0"
case 'f': { /* frontier? */
const char *ep; char previous;
p += 2;
if (*p != '[')
luaL_error(ms->L, "missing " LUA_QL("[") " after "
LUA_QL("%%f") " in pattern");
ep = classend(ms, p); /* points to what is next */
previous = (s == ms->src_init) ? '\0' : *(s-1);
//接口matchbracketclass用来判断某个字符是否模式中指定的字符集
if (matchbracketclass(uchar(previous), p, ep-1) ||
!matchbracketclass(uchar(*s), p, ep-1)) return NULL;
p=ep; goto init; /* else return match(ms, s, ep); */
}
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
case '8': case '9': { /* capture results (%0-%9)? */
//根据原来捕获的内容,匹配目标字符串,返回NULL或者目标字符串下一个查找的位置
//****************
static const char *match_capture (MatchState *ms, const char *s, int l) {
size_t len;
l = check_capture(ms, l);
len = ms->capture[l].len;
if ((size_t)(ms->src_end-s) >= len &&
memcmp(ms->capture[l].init, s, len) == 0)
return s+len;
else return NULL;
}
//****************
s = match_capture(ms, s, uchar(*(p+1)));
if (s == NULL) return NULL;
p+=2; goto init; /* else return match(ms, s, p+2) */
}
default: goto dflt;
}
}
default: dflt: { /* pattern class plus optional suffix */
const char *ep = classend(ms, p); /* points to what is next */
//接口singlematch用来判断当前字符*s是否匹配对应的模式中的字符
int m = s < ms->src_end && singlematch(uchar(*s), p, ep);
switch (*ep) {
case '?': { /* optional *///出现0次或1次
const char *res;
if (m && ((res=match(ms, s+1, ep+1)) != NULL))
return res;
p=ep+1; goto init; /* else return match(ms, s, ep+1); */
}
case '*': { /* 0 or more repetitions */
//max_expand表示匹配到的字符尽可能长,返回匹配的最后一个位置
return max_expand(ms, s, p, ep);
}
case '+': { /* 1 or more repetitions */
return (m ? max_expand(ms, s+1, p, ep) : NULL);
}
case '-': { /* 0 or more repetitions (minimum) */
return min_expand(ms, s, p, ep);
}
default: {
if (!m) return NULL;
s++; p=ep; goto init; /* else return match(ms, s+1, ep); */
}
}
}
}
}
match接口中用到start_capture,当一个捕获的开始时(即遇到字符"("时),或者针对"()"会调用该接口,代码如下:
//参数what用来标识,捕获是那种,是CAP_UNFINISHED还是CAP_POSITION的
static const char *start_capture (MatchState *ms, const char *s,
const char *p, int what) {
const char *res;
int level = ms->level;
if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, "too many captures");
ms->capture[level].init = s;
ms->capture[level].len = what;
ms->level = level+1;
if ((res=match(ms, s, p)) == NULL) /* match failed? */
ms->level--; /* undo capture */
return res;
}
当一个捕获结束时(即遇到字符")"时)接口,end_capture接口会被调用,代码如下:
static const char *end_capture (MatchState *ms, const char *s,
const char *p) {
//capture_to_close返回需要设置捕获结束的捕获的索引,存储捕获的状态类似于栈操作,代码如下:
/*
static int capture_to_close (MatchState *ms) {
int level = ms->level;
for (level--; level>=0; level--)
if (ms->capture[level].len == CAP_UNFINISHED) return level;
return luaL_error(ms->L, "invalid pattern capture");
}
*/
int l = capture_to_close(ms);
const char *res;
ms->capture[l].len = s - ms->capture[l].init; /* close capture */ //即捕获的部分字符串长度
if ((res = match(ms, s, p)) == NULL) /* match failed? */
ms->capture[l].len = CAP_UNFINISHED; /* undo capture */
return res;
}
接口matchbalance用来匹配成对的字符,返回成对的字符串在目标字符串中的下一个位置,代码如下:
static const char *matchbalance (MatchState *ms, const char *s,
const char *p) {
if (p >= ms->p_end - 1)
//模式中配对的边界字符必须有两个,参数*p是边界字符的第一个字符
luaL_error(ms->L, "malformed pattern "
"(missing arguments to " LUA_QL("%%b") ")");
if (*s != *p) return NULL;
else {
int b = *p;
int e = *(p+1);
int cont = 1;
//从这里可以看到,匹配成对的字符边界是尽可能大的
while (++s < ms->src_end) {
if (*s == e) {
if (--cont == 0) return s+1;
}
else if (*s == b) cont++;
}
}
return NULL; /* string ends out of balance */
}
接口classend用来分类的字符的下一个位置,比如模式"[a13]b"传入的位置执行字符"[",则返回的指针指向的字符"b",代码如下:
static const char *classend (MatchState *ms, const char *p) {
switch (*p++) {
case L_ESC: {
if (p == ms->p_end)
luaL_error(ms->L, "malformed pattern (ends with " LUA_QL("%%") ")");
return p+1;
}
case '[': {
if (*p == '^') p++;
do { /* look for a `]' */
if (p == ms->p_end)
luaL_error(ms->L, "malformed pattern (missing " LUA_QL("]") ")");
if (*(p++) == L_ESC && p < ms->p_end)
p++; /* skip escapes (e.g. `%]') */
} while (*p != ']');
return p+1;
}
default: {
return p; //返回下一个位置即可
}
}
}
接口matchbracketclass用来判断某个字符是否模式中指定的字符集,代码如下:
//参数p和ec分别指向字符集在模式中的第一个位置和最后一个位置
static int matchbracketclass (int c, const char *p, const char *ec) {
int sig = 1; //用来标识是否取字符集的补集
if (*(p+1) == '^') {
sig = 0;
p++; /* skip the `^' */
}
while (++p < ec) {
if (*p == L_ESC) {
//%a,%c等设定好的分类字符
p++;
//match_class用来确定c是否是指定分类的字符
if (match_class(c, uchar(*p)))
return sig;
}
else if ((*(p+1) == '-') && (p+2 < ec)) {
p+=2;
if (uchar(*(p-2)) <= c && c <= uchar(*p)) //针对区间字符分类的情况
return sig;
}
else if (uchar(*p) == c) return sig;
}
return !sig;
}
match_class的代码如下:
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 'g' : res = isgraph(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; /* deprecated option */
default: return (cl == c); //若模式不是分类字符,则只做简单的比较
}
return (islower(cl) ? res : !res); //按设定,大写模式字符类,是取反的
}
接口singlematch用来判断当前字符*s是否匹配对应的模式中的字符,代码如下:
//参数p和ep分别指向模式中,当前要匹配的字符的开始位置和最后一个位置的下一个位置
static int singlematch (int c, const char *p, const char *ep) {
switch (*p) {
case '.': return 1; /* matches any char */
case L_ESC: return match_class(c, uchar(*(p+1)));
case '[': return matchbracketclass(c, p, ep-1);
default: return (uchar(*p) == c);
}
}
max_expand表示匹配到的字符尽可能长,返回匹配的最后一个位置,代码如下:
static const char *max_expand (MatchState *ms, const char *s,
const char *p, const char *ep) {
ptrdiff_t i = 0; /* counts maximum expand for item */
while ((s+i)<ms->src_end && singlematch(uchar(*(s+i)), p, ep))
i++;
/* keeps trying to match with the maximum repetitions */
while (i>=0) {
//保存后面的模式与目标字符串能匹配到才行
const char *res = match(ms, (s+i), ep+1);
if (res) return res;
i--; /* else didn't match; reduce 1 repetition to try again */
}
return NULL;
}
对应的min_expand,代码如下:
static const char *min_expand (MatchState *ms, const char *s,
const char *p, const char *ep) {
for (;;) {
const char *res = match(ms, s, ep+1);
if (res != NULL)
return res;
else if (s<ms->src_end && singlematch(uchar(*s), p, ep))
s++; /* try with one more repetition */
else return NULL;
}
}
在str_find_aux()中调用了接口push_captures()把捕获的结果的,压入栈中,返回,实现代码如下:
static int push_captures (MatchState *ms, const char *s, const char *e) {
int i;
int nlevels = (ms->level == 0 && s) ? 1 : ms->level;
luaL_checkstack(ms->L, nlevels, "too many captures");
for (i = 0; i < nlevels; i++)
push_onecapture(ms, i, s, e);
return nlevels; /* number of strings pushed */
}
接口push_onecapture的代码如下:
static void push_onecapture (MatchState *ms, int i, const char *s,
const char *e) {
if (i >= ms->level) {
if (i == 0) /* ms->level == 0, too */
lua_pushlstring(ms->L, s, e - s); /* add whole match *///match没有捕获时,则返回匹配到的字符串
else
luaL_error(ms->L, "invalid capture index");
}
else {
//对find和match,模式中有捕获,则都会返回捕获的内容
ptrdiff_t l = ms->capture[i].len;
if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture");
if (l == CAP_POSITION) //针对模式中"()"捕捉,只返回相应在目标字符串中的位置
lua_pushinteger(ms->L, ms->capture[i].init - ms->src_init + 1);
else
lua_pushlstring(ms->L, ms->capture[i].init, l); //返回在目标字符串中捕捉的字符串
}
}
下面来看string.gmatch()的实现,它返回一个函数(实质上是一个闭包),通过这个函数可以遍历一个字符串中所有出现指定模式的地方。例如:以下代码可找出给定字符串s中所有的单词:
words = {}
for w in string.gmatch(s,"a+") do
words[#words + 1] = w
end
string.gmatch实现代码如下:
static int gmatch (lua_State *L) {
luaL_checkstring(L, 1);
luaL_checkstring(L, 2);
lua_settop(L, 2);
lua_pushinteger(L, 0); //该upvalue用来标识下次在目标字符串开始查找的位置
lua_pushcclosure(L, gmatch_aux, 3); //返回一个闭包
return 1;
}
static int gmatch_aux (lua_State *L) {
MatchState ms;
size_t ls, lp;
const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls);
const char *p = lua_tolstring(L, lua_upvalueindex(2), &lp);
const char *src;
ms.L = L;
ms.src_init = s;
ms.src_end = s+ls;
ms.p_end = p + lp;
for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3));
src <= ms.src_end;
src++) {
const char *e;
ms.level = 0;
if ((e = match(&ms, src, p)) != NULL) {
lua_Integer newstart = e-s;
if (e == src) newstart++; /* empty match? go at least one position */
lua_pushinteger(L, newstart);
lua_replace(L, lua_upvalueindex(3)); //下一次查找的位置
return push_captures(&ms, src, e); //返回匹配到的字符串或捕获的多个字符串
}
}
return 0; /* not found */
}
下面来看string.gsub()的实现,代码如下:
static int str_gsub (lua_State *L) {
size_t srcl, lp;
const char *src = luaL_checklstring(L, 1, &srcl);
const char *p = luaL_checklstring(L, 2, &lp);
int tr = lua_type(L, 3); //第三个参数的类型
size_t max_s = luaL_optinteger(L, 4, srcl+1); //第四个参数,表示替换的次数,可选的,默认是全部替换
int anchor = (*p == '^');
size_t n = 0;
MatchState ms;
luaL_Buffer b;
//第三个参数,必须是指定的四种类型
luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING ||
tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3,
"string/function/table expected");
luaL_buffinit(L, &b);
if (anchor) {
p++; lp--; /* skip anchor character */
}
ms.L = L;
ms.src_init = src;
ms.src_end = src+srcl;
ms.p_end = p + lp;
while (n < max_s) {
const char *e;
ms.level = 0;
//src始终保存目标字符串开始匹配的位置
//e保存当前匹配到的模式在目标字符串中的位置
e = match(&ms, src, p);
if (e) {
n++;
add_value(&ms, &b, src, e, tr);
}
if (e && e>src) /* non empty match? */
src = e; /* skip it */
else if (src < ms.src_end)
//没有匹配到,或者为空
luaL_addchar(&b, *src++);
else break;
if (anchor) break; //只匹配目标字符串的开始位置,即最多替换一次
}
luaL_addlstring(&b, src, ms.src_end-src);
luaL_pushresult(&b);
lua_pushinteger(L, n); /* number of substitutions */
return 2;
}
上面使用的接口add_value(),实现如下:
static void add_value (MatchState *ms, luaL_Buffer *b, const char *s,
const char *e, int tr) {
lua_State *L = ms->L;
switch (tr) {
case LUA_TFUNCTION: {
int n;
lua_pushvalue(L, 3);
n = push_captures(ms, s, e); //以捕获到的字符串作为参数,可以有多个参数,若没有捕获,即匹配到的字符串作为参数
lua_call(L, n, 1);
break;
}
case LUA_TTABLE: {
push_onecapture(ms, 0, s, e); //若有捕获,则以第一个捕获做key,否则则以匹配到的整个字符串作为key,获取对应的value
lua_gettable(L, 3);
break;
}
default: { /* LUA_TNUMBER or LUA_TSTRING */
add_s(ms, b, s, e);
return;
}
}
if (!lua_toboolean(L, -1)) { /* nil or false? */ //函数返回nil或false,或者key在table中找不到相应的值
lua_pop(L, 1);
lua_pushlstring(L, s, e - s); /* keep original text */
}
else if (!lua_isstring(L, -1))
luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1));
luaL_addvalue(b); /* add result to accumulator */ //返回的结果必然是能转换为字符串
}
上面用到的add_s()接口,实现如下:
static void add_s (MatchState *ms, luaL_Buffer *b, const char *s,
const char *e) {
size_t l, i;
const char *news = lua_tolstring(ms->L, 3, &l);
for (i = 0; i < l; i++) {
if (news[i] != L_ESC)
luaL_addchar(b, news[i]);
else {
i++; /* skip ESC */
if (!isdigit(uchar(news[i]))) {
if (news[i] != L_ESC)
luaL_error(ms->L, "invalid use of " LUA_QL("%c")
" in replacement string", L_ESC);
luaL_addchar(b, news[i]); //替换字符串中的"%%",只压入"%"
}
else if (news[i] == '0')
luaL_addlstring(b, s, e - s); //替换字符串中的"%0",即表示整个匹配
else {
push_onecapture(ms, news[i] - '1', s, e); //压入相应的捕获
luaL_addvalue(b); /* add capture to accumulated result */
}
}
}
}
模块lstrlib.c中,剩下的只有string.format()的实现了,lua的字符串格式化与C非常类似,在这里不分析了。
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()并且第四个参数为true,表明忽略模式中的特殊字符,只做简单的原始匹配
//或者模式中,不存在特殊字符,也是做简单的原始匹配,这些特殊字符,包括"^$*+?.([%-",它是用
//宏SPECIALS来定义的,利用内部接口nospecials来判断模式中是否有特殊字符
if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) {
/* do a plain search */
const char *s2 = lmemfind(s + init - 1, ls - init + 1, p, lp);
if (s2) {
//返回匹配到的开始位置和结束位置
lua_pushinteger(L, s2 - s + 1);
lua_pushinteger(L, s2 - s + lp);
return 2;
}
}
else {
/*结构体MatchState用来保存当前匹配的情况,在用递归方法解决某个问题时,引入这样一个结构体,
函数调用之间通过这个结构体来传递信息是非常方便的*/
//这里的结构体MatchState定义如下:
/**************************************/
typedef struct MatchState {
const char *src_init; /* init of source string */
const char *src_end; /* end ('\0') of source string */
const char *p_end; /* end ('\0') of pattern */
lua_State *L;
int level; /* total number of captures (finished or unfinished) */
struct {
const char *init;
ptrdiff_t len;
} capture[LUA_MAXCAPTURES];
} MatchState;
/**************************************/
MatchState ms;
const char *s1 = s + init - 1; //目标字符串开始匹配的位置
int anchor = (*p == '^'); //用来标识是否只会匹配目标字符串的开头部分,准确的讲是指定位置算为开头位置
if (anchor) {
p++; lp--; /* skip anchor character */
}
//初始化状态结构体
ms.L = L;
ms.src_init = s;
ms.src_end = s + ls;
ms.p_end = p + lp;
do {
const char *res;
ms.level = 0;
if ((res=match(&ms, s1, p)) != NULL) {
if (find) {
lua_pushinteger(L, s1 - s + 1); /* start */
lua_pushinteger(L, res - s); /* end */
return push_captures(&ms, NULL, 0) + 2; //同时返回需要捕获到的内容
}
else
return push_captures(&ms, s1, res);
}
} while (s1++ < ms.src_end && !anchor); //从目标字符串下一个位置开始匹配,模式开始位置有"^"标识,表明只匹配开始位置,后面的位置都不用匹配了
}
lua_pushnil(L); /* not found */
return 1;
}
上面接口用nospecials(),用来检测模式字符串中,是否有特殊字符,代码如下:
/* check whether pattern has no special characters */
#define SPECIALS"^$*+?.([%-"
static int nospecials (const char *p, size_t l) {
size_t upto = 0;
do {
//查找p+upto开始的字符串中,是否存在SPECIALS中的字符,并返回第一次出现的位置
if (strpbrk(p + upto, SPECIALS))
return 0; /* pattern has a special character */
upto += strlen(p + upto) + 1; /* may have more after \0 */
} while (upto <= l);
return 1; /* no special chars found */
}
上面接口用lmemfind()来做简单的字符匹配,代码如下:
static const char *lmemfind (const char *s1, size_t l1,
const char *s2, size_t l2) {
if (l2 == 0) return s1; /* empty strings are everywhere */
else if (l2 > l1) return NULL; /* avoids a negative `l1' */
else {
const char *init; /* to search for a `*s2' inside `s1' */
l2--; /* 1st char will be checked by `memchr' *///即下面的while循环判断
l1 = l1-l2; /* `s2' cannot be found after that */
//memchr返回值为*s2在由s1指向的内存中第一次出现的位置,查找过程中是按字节比对的,只比较l1个字节
while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) {
init++; /* 1st char is already checked */
if (memcmp(init, s2+1, l2) == 0)
return init-1;
else { /* correct `l1' and `s1' to try again */
l1 -= init-s1; //前面比较的位置,现在就可以忽略了
s1 = init; //挪到下一个位置开始比对
}
}
return NULL; /* not found */
}
}
在模式匹配中,主要用到接口match,该接口使用了goto,用来优化尾递归,这样做代码看起来逻辑可能更清楚,得到的启发是,没有什么是绝对不能使用的,下面我们来看该接口的实现:
static const char *match (MatchState *ms, const char *s, const char *p) {
init: /* using goto's to optimize tail recursion */
if (p == ms->p_end) /* end of pattern? */
return s; /* match succeeded */ //表明匹配类似""这样的空字符串,返回的指针指向目标字符串开始位置,这里也是结束递归的位置
switch (*p) {
case '(': { /* start capture */
if (*(p+1) == ')') /* position capture? */ //CAP_POSITION用来针对模式中"()"的捕获,从这里可以看到,是目标字符串匹配当前位置s,作为捕获的起始位置,并用这个特殊标识CAP_POSITION。这种捕获也是占一个位置的。
return start_capture(ms, s, p+2, CAP_POSITION);
else
return start_capture(ms, s, p+1, CAP_UNFINISHED); //标识CAP_UNFINISHED用来当前捕获还正在匹配
}
case ')': { /* end capture */
return end_capture(ms, s, p+1);
}
case '$': {
if ((p+1) == ms->p_end) /* is the `$' the last char in pattern? */
//若"$"是模式中最后一个字符,则模块只会匹配目标字符串的结尾部分
return (s == ms->src_end) ? s : NULL; /* check end of string */
else goto dflt;
}
//L_ESC是lua中模式匹配中的转义字符,定义为#define L_ESC'%'
case L_ESC: { /* escaped sequences not in the format class[*+?-]? */
switch (*(p+1)) {
//模式是中%b,用于匹配成对的字符,例如%b()可匹配以"("开始,并以")"结束的子串
case 'b': { /* balanced string? */
s = matchbalance(ms, s, p+2); //返回匹配成对字符串在目标字符串中的下一个位置
if (s == NULL) return NULL;
p+=4; goto init; /* else return match(ms, s, p+4); */
}
//%f[set]要求前一个字符不属于集合set,并且当前字符属于集合set。对于第一个字符,则前一个
//字符设定为"\0"
case 'f': { /* frontier? */
const char *ep; char previous;
p += 2;
if (*p != '[')
luaL_error(ms->L, "missing " LUA_QL("[") " after "
LUA_QL("%%f") " in pattern");
ep = classend(ms, p); /* points to what is next */
previous = (s == ms->src_init) ? '\0' : *(s-1);
//接口matchbracketclass用来判断某个字符是否模式中指定的字符集
if (matchbracketclass(uchar(previous), p, ep-1) ||
!matchbracketclass(uchar(*s), p, ep-1)) return NULL;
p=ep; goto init; /* else return match(ms, s, ep); */
}
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
case '8': case '9': { /* capture results (%0-%9)? */
//根据原来捕获的内容,匹配目标字符串,返回NULL或者目标字符串下一个查找的位置
//****************
static const char *match_capture (MatchState *ms, const char *s, int l) {
size_t len;
l = check_capture(ms, l);
len = ms->capture[l].len;
if ((size_t)(ms->src_end-s) >= len &&
memcmp(ms->capture[l].init, s, len) == 0)
return s+len;
else return NULL;
}
//****************
s = match_capture(ms, s, uchar(*(p+1)));
if (s == NULL) return NULL;
p+=2; goto init; /* else return match(ms, s, p+2) */
}
default: goto dflt;
}
}
default: dflt: { /* pattern class plus optional suffix */
const char *ep = classend(ms, p); /* points to what is next */
//接口singlematch用来判断当前字符*s是否匹配对应的模式中的字符
int m = s < ms->src_end && singlematch(uchar(*s), p, ep);
switch (*ep) {
case '?': { /* optional *///出现0次或1次
const char *res;
if (m && ((res=match(ms, s+1, ep+1)) != NULL))
return res;
p=ep+1; goto init; /* else return match(ms, s, ep+1); */
}
case '*': { /* 0 or more repetitions */
//max_expand表示匹配到的字符尽可能长,返回匹配的最后一个位置
return max_expand(ms, s, p, ep);
}
case '+': { /* 1 or more repetitions */
return (m ? max_expand(ms, s+1, p, ep) : NULL);
}
case '-': { /* 0 or more repetitions (minimum) */
return min_expand(ms, s, p, ep);
}
default: {
if (!m) return NULL;
s++; p=ep; goto init; /* else return match(ms, s+1, ep); */
}
}
}
}
}
match接口中用到start_capture,当一个捕获的开始时(即遇到字符"("时),或者针对"()"会调用该接口,代码如下:
//参数what用来标识,捕获是那种,是CAP_UNFINISHED还是CAP_POSITION的
static const char *start_capture (MatchState *ms, const char *s,
const char *p, int what) {
const char *res;
int level = ms->level;
if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, "too many captures");
ms->capture[level].init = s;
ms->capture[level].len = what;
ms->level = level+1;
if ((res=match(ms, s, p)) == NULL) /* match failed? */
ms->level--; /* undo capture */
return res;
}
当一个捕获结束时(即遇到字符")"时)接口,end_capture接口会被调用,代码如下:
static const char *end_capture (MatchState *ms, const char *s,
const char *p) {
//capture_to_close返回需要设置捕获结束的捕获的索引,存储捕获的状态类似于栈操作,代码如下:
/*
static int capture_to_close (MatchState *ms) {
int level = ms->level;
for (level--; level>=0; level--)
if (ms->capture[level].len == CAP_UNFINISHED) return level;
return luaL_error(ms->L, "invalid pattern capture");
}
*/
int l = capture_to_close(ms);
const char *res;
ms->capture[l].len = s - ms->capture[l].init; /* close capture */ //即捕获的部分字符串长度
if ((res = match(ms, s, p)) == NULL) /* match failed? */
ms->capture[l].len = CAP_UNFINISHED; /* undo capture */
return res;
}
接口matchbalance用来匹配成对的字符,返回成对的字符串在目标字符串中的下一个位置,代码如下:
static const char *matchbalance (MatchState *ms, const char *s,
const char *p) {
if (p >= ms->p_end - 1)
//模式中配对的边界字符必须有两个,参数*p是边界字符的第一个字符
luaL_error(ms->L, "malformed pattern "
"(missing arguments to " LUA_QL("%%b") ")");
if (*s != *p) return NULL;
else {
int b = *p;
int e = *(p+1);
int cont = 1;
//从这里可以看到,匹配成对的字符边界是尽可能大的
while (++s < ms->src_end) {
if (*s == e) {
if (--cont == 0) return s+1;
}
else if (*s == b) cont++;
}
}
return NULL; /* string ends out of balance */
}
接口classend用来分类的字符的下一个位置,比如模式"[a13]b"传入的位置执行字符"[",则返回的指针指向的字符"b",代码如下:
static const char *classend (MatchState *ms, const char *p) {
switch (*p++) {
case L_ESC: {
if (p == ms->p_end)
luaL_error(ms->L, "malformed pattern (ends with " LUA_QL("%%") ")");
return p+1;
}
case '[': {
if (*p == '^') p++;
do { /* look for a `]' */
if (p == ms->p_end)
luaL_error(ms->L, "malformed pattern (missing " LUA_QL("]") ")");
if (*(p++) == L_ESC && p < ms->p_end)
p++; /* skip escapes (e.g. `%]') */
} while (*p != ']');
return p+1;
}
default: {
return p; //返回下一个位置即可
}
}
}
接口matchbracketclass用来判断某个字符是否模式中指定的字符集,代码如下:
//参数p和ec分别指向字符集在模式中的第一个位置和最后一个位置
static int matchbracketclass (int c, const char *p, const char *ec) {
int sig = 1; //用来标识是否取字符集的补集
if (*(p+1) == '^') {
sig = 0;
p++; /* skip the `^' */
}
while (++p < ec) {
if (*p == L_ESC) {
//%a,%c等设定好的分类字符
p++;
//match_class用来确定c是否是指定分类的字符
if (match_class(c, uchar(*p)))
return sig;
}
else if ((*(p+1) == '-') && (p+2 < ec)) {
p+=2;
if (uchar(*(p-2)) <= c && c <= uchar(*p)) //针对区间字符分类的情况
return sig;
}
else if (uchar(*p) == c) return sig;
}
return !sig;
}
match_class的代码如下:
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 'g' : res = isgraph(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; /* deprecated option */
default: return (cl == c); //若模式不是分类字符,则只做简单的比较
}
return (islower(cl) ? res : !res); //按设定,大写模式字符类,是取反的
}
接口singlematch用来判断当前字符*s是否匹配对应的模式中的字符,代码如下:
//参数p和ep分别指向模式中,当前要匹配的字符的开始位置和最后一个位置的下一个位置
static int singlematch (int c, const char *p, const char *ep) {
switch (*p) {
case '.': return 1; /* matches any char */
case L_ESC: return match_class(c, uchar(*(p+1)));
case '[': return matchbracketclass(c, p, ep-1);
default: return (uchar(*p) == c);
}
}
max_expand表示匹配到的字符尽可能长,返回匹配的最后一个位置,代码如下:
static const char *max_expand (MatchState *ms, const char *s,
const char *p, const char *ep) {
ptrdiff_t i = 0; /* counts maximum expand for item */
while ((s+i)<ms->src_end && singlematch(uchar(*(s+i)), p, ep))
i++;
/* keeps trying to match with the maximum repetitions */
while (i>=0) {
//保存后面的模式与目标字符串能匹配到才行
const char *res = match(ms, (s+i), ep+1);
if (res) return res;
i--; /* else didn't match; reduce 1 repetition to try again */
}
return NULL;
}
对应的min_expand,代码如下:
static const char *min_expand (MatchState *ms, const char *s,
const char *p, const char *ep) {
for (;;) {
const char *res = match(ms, s, ep+1);
if (res != NULL)
return res;
else if (s<ms->src_end && singlematch(uchar(*s), p, ep))
s++; /* try with one more repetition */
else return NULL;
}
}
在str_find_aux()中调用了接口push_captures()把捕获的结果的,压入栈中,返回,实现代码如下:
static int push_captures (MatchState *ms, const char *s, const char *e) {
int i;
int nlevels = (ms->level == 0 && s) ? 1 : ms->level;
luaL_checkstack(ms->L, nlevels, "too many captures");
for (i = 0; i < nlevels; i++)
push_onecapture(ms, i, s, e);
return nlevels; /* number of strings pushed */
}
接口push_onecapture的代码如下:
static void push_onecapture (MatchState *ms, int i, const char *s,
const char *e) {
if (i >= ms->level) {
if (i == 0) /* ms->level == 0, too */
lua_pushlstring(ms->L, s, e - s); /* add whole match *///match没有捕获时,则返回匹配到的字符串
else
luaL_error(ms->L, "invalid capture index");
}
else {
//对find和match,模式中有捕获,则都会返回捕获的内容
ptrdiff_t l = ms->capture[i].len;
if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture");
if (l == CAP_POSITION) //针对模式中"()"捕捉,只返回相应在目标字符串中的位置
lua_pushinteger(ms->L, ms->capture[i].init - ms->src_init + 1);
else
lua_pushlstring(ms->L, ms->capture[i].init, l); //返回在目标字符串中捕捉的字符串
}
}
下面来看string.gmatch()的实现,它返回一个函数(实质上是一个闭包),通过这个函数可以遍历一个字符串中所有出现指定模式的地方。例如:以下代码可找出给定字符串s中所有的单词:
words = {}
for w in string.gmatch(s,"a+") do
words[#words + 1] = w
end
string.gmatch实现代码如下:
static int gmatch (lua_State *L) {
luaL_checkstring(L, 1);
luaL_checkstring(L, 2);
lua_settop(L, 2);
lua_pushinteger(L, 0); //该upvalue用来标识下次在目标字符串开始查找的位置
lua_pushcclosure(L, gmatch_aux, 3); //返回一个闭包
return 1;
}
static int gmatch_aux (lua_State *L) {
MatchState ms;
size_t ls, lp;
const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls);
const char *p = lua_tolstring(L, lua_upvalueindex(2), &lp);
const char *src;
ms.L = L;
ms.src_init = s;
ms.src_end = s+ls;
ms.p_end = p + lp;
for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3));
src <= ms.src_end;
src++) {
const char *e;
ms.level = 0;
if ((e = match(&ms, src, p)) != NULL) {
lua_Integer newstart = e-s;
if (e == src) newstart++; /* empty match? go at least one position */
lua_pushinteger(L, newstart);
lua_replace(L, lua_upvalueindex(3)); //下一次查找的位置
return push_captures(&ms, src, e); //返回匹配到的字符串或捕获的多个字符串
}
}
return 0; /* not found */
}
下面来看string.gsub()的实现,代码如下:
static int str_gsub (lua_State *L) {
size_t srcl, lp;
const char *src = luaL_checklstring(L, 1, &srcl);
const char *p = luaL_checklstring(L, 2, &lp);
int tr = lua_type(L, 3); //第三个参数的类型
size_t max_s = luaL_optinteger(L, 4, srcl+1); //第四个参数,表示替换的次数,可选的,默认是全部替换
int anchor = (*p == '^');
size_t n = 0;
MatchState ms;
luaL_Buffer b;
//第三个参数,必须是指定的四种类型
luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING ||
tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3,
"string/function/table expected");
luaL_buffinit(L, &b);
if (anchor) {
p++; lp--; /* skip anchor character */
}
ms.L = L;
ms.src_init = src;
ms.src_end = src+srcl;
ms.p_end = p + lp;
while (n < max_s) {
const char *e;
ms.level = 0;
//src始终保存目标字符串开始匹配的位置
//e保存当前匹配到的模式在目标字符串中的位置
e = match(&ms, src, p);
if (e) {
n++;
add_value(&ms, &b, src, e, tr);
}
if (e && e>src) /* non empty match? */
src = e; /* skip it */
else if (src < ms.src_end)
//没有匹配到,或者为空
luaL_addchar(&b, *src++);
else break;
if (anchor) break; //只匹配目标字符串的开始位置,即最多替换一次
}
luaL_addlstring(&b, src, ms.src_end-src);
luaL_pushresult(&b);
lua_pushinteger(L, n); /* number of substitutions */
return 2;
}
上面使用的接口add_value(),实现如下:
static void add_value (MatchState *ms, luaL_Buffer *b, const char *s,
const char *e, int tr) {
lua_State *L = ms->L;
switch (tr) {
case LUA_TFUNCTION: {
int n;
lua_pushvalue(L, 3);
n = push_captures(ms, s, e); //以捕获到的字符串作为参数,可以有多个参数,若没有捕获,即匹配到的字符串作为参数
lua_call(L, n, 1);
break;
}
case LUA_TTABLE: {
push_onecapture(ms, 0, s, e); //若有捕获,则以第一个捕获做key,否则则以匹配到的整个字符串作为key,获取对应的value
lua_gettable(L, 3);
break;
}
default: { /* LUA_TNUMBER or LUA_TSTRING */
add_s(ms, b, s, e);
return;
}
}
if (!lua_toboolean(L, -1)) { /* nil or false? */ //函数返回nil或false,或者key在table中找不到相应的值
lua_pop(L, 1);
lua_pushlstring(L, s, e - s); /* keep original text */
}
else if (!lua_isstring(L, -1))
luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1));
luaL_addvalue(b); /* add result to accumulator */ //返回的结果必然是能转换为字符串
}
上面用到的add_s()接口,实现如下:
static void add_s (MatchState *ms, luaL_Buffer *b, const char *s,
const char *e) {
size_t l, i;
const char *news = lua_tolstring(ms->L, 3, &l);
for (i = 0; i < l; i++) {
if (news[i] != L_ESC)
luaL_addchar(b, news[i]);
else {
i++; /* skip ESC */
if (!isdigit(uchar(news[i]))) {
if (news[i] != L_ESC)
luaL_error(ms->L, "invalid use of " LUA_QL("%c")
" in replacement string", L_ESC);
luaL_addchar(b, news[i]); //替换字符串中的"%%",只压入"%"
}
else if (news[i] == '0')
luaL_addlstring(b, s, e - s); //替换字符串中的"%0",即表示整个匹配
else {
push_onecapture(ms, news[i] - '1', s, e); //压入相应的捕获
luaL_addvalue(b); /* add capture to accumulated result */
}
}
}
}
模块lstrlib.c中,剩下的只有string.format()的实现了,lua的字符串格式化与C非常类似,在这里不分析了。
- Lua源码剖析(lstrlib.c)
- lstrlib.c源码解析
- Lua源码剖析(lmathlib.c)
- lua源码剖析(二)
- Lua源码剖析-GC
- lua源码剖析(一)
- lua源码剖析(二)
- lua源码剖析(三)
- lua源码剖析1【转】
- lua源码剖析2【转】
- lua源码剖析3【转】
- Lua 源码剖析第1天
- lua源码lvm 最核心部分剖析
- c/c++:strlen源码剖析
- C++STL源码剖析代码
- openresty源码剖析——lua代码的加载
- openresty源码剖析——lua代码的加载
- openresty源码剖析——lua代码的加载
- C++ 适配器模式
- linux数据链路访问之ETH_P_ALL等等
- MYSQL管理之主从同步管理
- 演示馆-走向世界之《洋话连篇 自助风暴系列之高级版》英语口语视频教程
- 基本类型与引用类型
- Lua源码剖析(lstrlib.c)
- c++ string操作
- android 微博客客户端
- c++ 流 处理 缓冲区
- Mac下搭建Java服务端开发环境
- 118A_String Task
- I2C总线的EEPROM(24C08)Linux驱动
- eclipse如果去掉theme
- GS1011无线模块的使用简介。