字串插補 |
|
print("Hello " .. name .. ", the value of key " .. k .. " is " .. v .. "!")
與 Perl 比較,其中的變數可以內嵌到字串
print "Hello $name, the value of key $k is $b!\n";
關於 Lua 版的抱怨是,引用冗長且會讓它更難閱讀,例如在視覺上難以區分文字在引號內或外。除了使用有語法突顯功能的編輯器,後者問題可用括弧引用樣式來改善
print([[Hello ]] .. name .. [[, the value of key ]] .. k .. [[ is ]] .. v .. [[!]])
也可以用 string.format
讓它更簡潔
print(string.format("Hello %s, the value of key %s is %s", name, k, v))
可能使用輔助函式
function printf(...) print(string.format(...)) end printf("Hello %s, the value of key %s is %s", name, k, v)
這提出的新問題是變數以位置來識別,如果變數數量龐大,會產生可讀性和維護性問題。
下列解法顯示如何於 Lua 中實作將變數插補到字串中的支援,以達成類似以下的語法
printf("Hello %(name), the value of key %(k) is %(v)")
這裡有一個簡單的實作 (-- RiciLake)
function interp(s, tab) return (s:gsub('($%b{})', function(w) return tab[w:sub(3, -2)] or w end)) end print( interp("${name} is ${value}", {name = "foo", value = "bar"}) ) getmetatable("").__mod = interp print( "${name} is ${value}" % {name = "foo", value = "bar"} ) -- Outputs "foo is bar"
這裡有另一個支援 Pythonic 格式化規範的實作 (-- RiciLake)(需要 Lua 5.1 以上版本)
function interp(s, tab) return (s:gsub('%%%((%a%w*)%)([-0-9%.]*[cdeEfgGiouxXsq])', function(k, fmt) return tab[k] and ("%"..fmt):format(tab[k]) or '%('..k..')'..fmt end)) end getmetatable("").__mod = interp print( "%(key)s is %(val)7.2f%" % {key = "concentration", val = 56.2795} ) -- outputs "concentration is 56.28%"
這裡有另一個僅限 Lua 的解法 (-- MarkEdgar)
function replace_vars(str, vars) -- Allow replace_vars{str, vars} syntax as well as replace_vars(str, {vars}) if not vars then vars = str str = vars[1] end return (string_gsub(str, "({([^}]+)})", function(whole,i) return vars[i] or whole end)) end -- Example: output = replace_vars{ [[Hello {name}, welcome to {company}. ]], name = name, company = get_company_name() }
Ruby 與 Python 都有使用 % 運算子的簡短字串格式化形式。
下列程式片段增加 lua 中類似 mod 運算子的用法
getmetatable("").__mod = function(a, b) if not b then return a elseif type(b) == "table" then return string.format(a, unpack(b)) else return string.format(a, b) end end
範例用法
print( "%5.2f" % math.pi ) print( "%-10.10s %04d" % { "test", 123 } )
您可能喜歡或不喜歡這個表示法,請自行選擇。
以下是比較複雜的實作 (-- DavidManura)。這使用 debug 函式庫(特別是 debug.getlocal())來查詢局部變數,這可能由於各種理由而不理想 (-- RiciLake)。首先,它可用來破解不該破解的事物,因此如果執行受信任的程式碼,這會是個壞主意。debug.getlocal() 也很耗效能,因為它需要掃描整個位元組碼,才能找出哪些變數在作用域內。它也不會擷取封閉變數。
程式碼
-- "nil" value that can be stored in tables. local mynil_mt = {__tostring = function() return tostring(nil) end} local mynil = setmetatable({}, mynil_mt) -- Retrieves table of all local variables (name, value) -- in given function <func>. If a value is Nil, it instead -- stores the value <mynil> in the table to distinguish a -- a local variable that is nil from the local variable not -- existing. -- If a number is given in place of <func>, then it -- uses that level in the call stack. Level 1 is the -- function that called get_locals. -- Note: this correctly handles the case where two locals have the -- same name: "local x = 1 ... get_locals(1) ... local x = 2". -- This function is similar and is based on debug.getlocal(). function get_locals(func) local n = 1 local locals = {} func = (type(func) == "number") and func + 1 or func while true do local lname, lvalue = debug.getlocal(func, n) if lname == nil then break end -- end of list if lvalue == nil then lvalue = mynil end -- replace locals[lname] = lvalue n = n + 1 end return locals end -- Interpolates variables into string <str>. -- Variables are defined in table <table>. If <table> is -- omitted, then it uses local and global variables in the -- calling function. -- Option level indicates the level in the call stack to -- obtain local variable from (1 if omitted). function interp(str, table, level) local use_locals = (table == nil) table = table or getfenv(2) if use_locals then level = level or 1 local locals = get_locals(level + 1) table = setmetatable(locals, {__index = table}) end local out = string.gsub(str, '$(%b{})', function(w) local variable_name = string.sub(w, 2, -2) local variable_value = table[variable_name] if variable_value == mynil then variable_value = nil end return tostring(variable_value) end ) return out end -- Interpolating print. -- This is just a wrapper around print and interp. -- It only accepts a single string argument. function printi(str) print(interp(str, nil, 2)) end -- Pythonic "%" operator for srting interpolation. getmetatable("").__mod = interp
測試
-- test globals x=123 assert(interp "x = ${x}" == "x = 123") -- test table assert(interp("x = ${x}", {x = 234}) == "x = 234") -- test locals (which override globals) do local x = 3 assert(interp "x = ${x}" == "x = 3") end -- test globals using setfenv function test() assert(interp "y = ${y}" == "y = 123") end local env = {y = 123} setmetatable(env, {__index = _G}) setfenv(test, env) test() -- test of multiple locals of same name do local z = 1 local z = 2 assert(interp "z = ${z}" == "z = 2") local z = 3 end -- test of locals with nil value do z = 2 local z = 1 local z = nil assert(interp "z = ${z}" == "z = nil") end -- test of printi x = 123 for k, v in ipairs {3,4} do printi("${x} - The value of key ${k} is ${v}") end -- test of "%" operator assert("x = ${x}" % {x = 2} == "x = 2")
可進行各種強化。例如,
v = {x = 2} print(interp "v.x = ${v.x}") -- not implemented
Ruby 與 PHP 中我喜歡的功能之一是能夠將變數包含在字串內,範例為 print "Hello ${Name}" 以下修補程式執行相同動作,但只適用於文件字串類型,開頭為 [[ 結尾為 ]] 的字串。它使用「|」字元來代表開啟和關閉大括號。
要在範例中新增內嵌變數
output = [[Hello |name|, welcome to |get_company_name()|. ]]
修補程式的運作方式基本上會將上面轉換成
output = [[Hello ]]..name..[[, welcome to ]]..get_company_name()..[[. ]]
以下函數已在 llex.c 檔中更新。
重要注意事項:不知何故,我需要另一個字元來表示程式碼中的閉括號,而我已任意選出「�」,這是什麼意思?如果你 somehow 在你的字串(特別是你使用外語編碼)中有那個字元,你會得到一個語法錯誤。我不知道目前是否有解決這個問題的方法。
int luaX_lex (LexState *LS, SemInfo *seminfo) { for (;;) { switch (LS->current) { case '\n': { inclinenumber(LS); continue; } case '-': { next(LS); if (LS->current != '-') return '-'; /* else is a comment */ next(LS); if (LS->current == '[' && (next(LS), LS->current == '[')) read_long_string(LS, NULL); /* long comment */ else /* short comment */ while (LS->current != '\n' && LS->current != EOZ) next(LS); continue; } case '[': { next(LS); if (LS->current != '[') return '['; else { read_long_string(LS, seminfo); return TK_STRING; } } case '=': { next(LS); if (LS->current != '=') return '='; else { next(LS); return TK_EQ; } } case '<': { next(LS); if (LS->current != '=') return '<'; else { next(LS); return TK_LE; } } case '>': { next(LS); if (LS->current != '=') return '>'; else { next(LS); return TK_GE; } } case '~': { next(LS); if (LS->current != '=') return '~'; else { next(LS); return TK_NE; } } case '"': case '\'': { read_string(LS, LS->current, seminfo); return TK_STRING; } // added!!! //------------------------------ case '|': { LS->current = '�'; return TK_CONCAT; } case '�': { read_long_string(LS, seminfo); return TK_STRING; } //------------------------------ case '.': { next(LS); if (LS->current == '.') { next(LS); if (LS->current == '.') { next(LS); return TK_DOTS; /* ... */ } else return TK_CONCAT; /* .. */ } else if (!isdigit(LS->current)) return '.'; else { read_numeral(LS, 1, seminfo); return TK_NUMBER; } } case EOZ: { return TK_EOS; } default: { if (isspace(LS->current)) { next(LS); continue; } else if (isdigit(LS->current)) { read_numeral(LS, 0, seminfo); return TK_NUMBER; } else if (isalpha(LS->current) || LS->current == '_') { /* identifier or reserved word */ size_t l = readname(LS); TString *ts = luaS_newlstr(LS->L, luaZ_buffer(LS->buff), l); if (ts->tsv.reserved > 0) /* reserved word? */ return ts->tsv.reserved - 1 + FIRST_RESERVED; seminfo->ts = ts; return TK_NAME; } else { int c = LS->current; if (iscntrl(c)) luaX_error(LS, "invalid control char", luaO_pushfstring(LS->L, "char(%d)", c)); next(LS); return c; /* single-char tokens (+ - / ...) */ } } } } } static void read_long_string (LexState *LS, SemInfo *seminfo) { int cont = 0; size_t l = 0; checkbuffer(LS, l); save(LS, '[', l); /* save first `[' */ save_and_next(LS, l); /* pass the second `[' */ if (LS->current == '\n') /* string starts with a newline? */ inclinenumber(LS); /* skip it */ for (;;) { checkbuffer(LS, l); switch (LS->current) { case EOZ: save(LS, '\0', l); luaX_lexerror(LS, (seminfo) ? "unfinished long string" : "unfinished long comment", TK_EOS); break; /* to avoid warnings */ case '[': save_and_next(LS, l); if (LS->current == '[') { cont++; save_and_next(LS, l); } continue; case ']': save_and_next(LS, l); if (LS->current == ']') { if (cont == 0) goto endloop; cont--; save_and_next(LS, l); } continue; // added //------------------------------ case '|': save(LS, ']', l); LS->lookahead.token = TK_CONCAT; goto endloop; continue; //------------------------------ case '\n': save(LS, '\n', l); inclinenumber(LS); if (!seminfo) l = 0; /* reset buffer to avoid wasting space */ continue; default: save_and_next(LS, l); } } endloop: save_and_next(LS, l); /* skip the second `]' */ save(LS, '\0', l); if (seminfo) seminfo->ts = luaS_newlstr(LS->L, luaZ_buffer(LS->buff) + 2, l - 5); }
--Sam Lie
有關 MetaLua 的實作,請參閱 MetaLuaRecipes 中的「字串內插」。
請參閱 [gist1338609](--DavidManura),它會安裝一個自訂的搜尋函數,用來預處理正在載入的模組。給定的範例預處理器會進行字串內插
--! code = require 'interpolate' (code) local M = {} local function printf(s, ...) local vals = {...} local i = 0 s = s:gsub('\0[^\0]*\0', function() i = i + 1 return tostring(vals[i]) end) print(s) end function M.test() local x = 16 printf("value is $(math.sqrt(x)) ") end return M
f"$(x) $(y)"
成為 f("$(x) $(y)",x,y)
的語法糖
在字串內嵌入表達式可以使用於這些應用程式