Lua学习与交流——Lua string库经验分享

来源:互联网 发布:最游记一键淘宝端 编辑:程序博客网 时间:2024/04/30 14:13

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() 的用法,我用两种方式来写吧。
第一种:
function ConvertSpecialChar(str)    return string.gsub(str, "[%(%)%.%%%+%-%*%?%[%]%^%$]",        function(ret)            ret = "%"..ret             return ret         end) end
参数2 是一个pattern,
参数3 是一个函数,string.gsub会把匹配得到的结果作为函数参数传入,函数内切莫忘了return。

第二种:
function ConvertSpecialChar(str)    return string.gsub(str, "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1") end
参数2 里多了一对括号,表示捕获截取到的字符串。
参数3 如果参数2里有多个括号,则 %1 对应第一个括号里捕获的内容,%2 对应第二个括号捕获的内容,一次类推。gsub会把捕获出的内容,按参数3的格式去替换原来的字符串。
至于第二种方案里,为什么要用到3个 % ,这个请大家自己去测试。

测试代码:
local TAG = "string.gsub: "print(TAG, ConvertSpecialChar("1+1+1++1+++1=2"))
看看是不是每个+号前都多了一个%?

有了上面的基础,再回过头来看看开始的例子:
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
0 0
原创粉丝点击