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