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非常类似,在这里不分析了。



























































原创粉丝点击