Lua string 库经验分享:
在部门里用Lua实现了一个RichText富文本,主要用到了一些Lua的模式匹配,想到之前也有好几次使用Lua string库的经验,虽然不多,也没怎么读过Lua的源码,但还是想拿出来与人分享。
Lua的模式匹配极为强大,掌握之后使用起来非常有乐趣,但期间很可能会遇到很多问题。比如在下就在string.find上跌倒过无数次。
string.find 注意事项:你的string.find 能 find 到吗?
测试以下代码:
local TAG = "string.find: "
print(TAG, string.find("1+1=2", "1+1"))
基础好的人一眼就看出来,这样是找不出结果的。原因很简单,没有处理Lua里的 Magic 字符,这些 Magic 字符包含:
( ) . % + - * ? [ ^ $
【注,Programming in Lua 一书中似乎漏掉了 "]"】
现在,我们需要一个方法,能把字符串里的魔法字符使用Lua的转义字符转义。这时候应该使用 string.gsub 了。
各位先思考一下,这个函数应该如何来写? 1秒过去了...... 2秒过去了...... 3秒过去了...... ...... 好了,为了方便大家理解 string.gsub() 的用法,我用两种方式来写吧。 第一种:
参数2 是一个pattern, 参数3 是一个函数,string.gsub会把匹配得到的结果作为函数参数传入,函数内切莫忘了return。 第二种:
参数2 里多了一对括号,表示捕获截取到的字符串。 参数3 如果参数2里有多个括号,则 %1 对应第一个括号里捕获的内容,%2 对应第二个括号捕获的内容,一次类推。gsub会把捕获出的内容,按参数3的格式去替换原来的字符串。 至于第二种方案里,为什么要用到3个 % ,这个请大家自己去测试。 测试代码:
看看是不是每个+号前都多了一个%? |
有了上面的基础,再回过头来看看开始的例子:
print(TAG, string.find("1+1=2", "1+1"))
理论上,是不是只要调用一下ConvertSpecialChar()来转换一下 string.find 里的第二个参数就可以了呢?
为了方便,我们直接这么写代码:
print(TAG, string.find("1+1=2", ConvertSpecialChar("1+1")))
测试一下,
输出: nil
!!!WTF!!!这样不对??
什么情况,赶紧再测试一下:
print(TAG, string.find("1+1=2", "1%+1"))
输出: 1 3
???Why???这样就是对的。
赶紧打印一下函数返回值。
print(ConvertSpecialChar("1+1"))
输出: 1%+1
这是正确的预期结果。
如果你也跟我一样迷糊,别担心,马上就不会揭晓谜底了。
这个错误是作者在实现RichText的时候,遇到的错误,测试检验了1个多小时,才弄明白为什么。
原来,
string.find()不接受函数作为参数,只接受字符串
顺便看了一下 lstrlib.c ,里面包含了 Lua 的string库实现。找到 str_find_aux 函数,是这样的:
static int str_find_aux (lua_State *L, int find) {
size_t l1, l2;
const char *s = luaL_checklstring(L, 1, &l1);
const char *p = luaL_checklstring(L, 2, &l2);
ptrdiff_t init = posrelat(luaL_optinteger(L, 3, 1), l1) - 1;
if (init < 0) init = 0;
else if ((size_t)(init) > l1) init = (ptrdiff_t)l1;
if (find && (lua_toboolean(L, 4) || /* explicit request? */
strpbrk(p, SPECIALS) == NULL)) { /* or no special characters? */
/* do a plain search */
const char *s2 = lmemfind(s+init, l1-init, p, l2);
if (s2) {
lua_pushinteger(L, s2-s+1);
lua_pushinteger(L, s2-s+l2);
return 2;
}
}
else {
MatchState ms;
int anchor = (*p == '^') ? (p++, 1) : 0;
const char *s1=s+init;
ms.L = L;
ms.src_init = s;
ms.src_end = s+l1;
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;
}
看到开头的 luaL_checklstring 差不多就知道所以然了。
(在下的Lua源码看得很少很少,很多东西只有用到的时候才会来看。)
所以,在有了 ConvertSpecialChar 函数后,要先调用函数,得到转换后的字符串,再去调用 string.find,才是正确的做法。
最后,如果大家有兴趣,可以再去测试一下如下代码:
local TAG = "string.find: "
print(TAG, string.find("1+1=2", ConvertSpecialChar("1+1").."="))
测试一下看能否得到正确的结果。
coming next...
string.gsub