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

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值