Goto 語法 |
|
goto
語句加入在 Lua 5.2.0-beta-rc1 [6] [1] 並在 5.2.0-beta-rc2 [7] 中微調。這是一種受限制型式的 goto
本文其餘內容將探討這個新結構的一些用法。
-- 5.2.0-beta-rc2 for z=1,10 do for y=1,10 do for x=1,10 do if x^2 + y^2 == z^2 then print('found a Pythagorean triple:', x, y, z) goto done end end end end ::done::
-- 5.2.0-beta-rc2 for z=1,10 do for y=1,10 do for x=1,10 do if x^2 + y^2 == z^2 then print('found a Pythagorean triple:', x, y, z) print('now trying next z...') goto zcontinue end end end ::zcontinue:: end
另見 ContinueProposal。
-- Lua 5.2.0-beta-rc2 for x=1,5 do ::redo:: print(x .. ' + 1 = ?') local y = tonumber(io.read'*l') if y ~= x + 1 then goto redo end end
-- Lua 5.2.0-beta-rc2 for _, x in ipairs(t) do if x % 2 == 0 then print 'list has even number' goto has end end print 'list lacks even number' ::has::
-- Lua 5.1 equivalent local has for _, x in ipairs(t) do if x % 2 == 0 then has = true break end end if has then print 'list has even number' else print 'list lacks even number' end
-- 5.2.0-beta-rc1 ::a:: print 'A' if math.random() < 0.3 then goto c end ::b:: print 'B' if math.random() < 0.5 then goto a end ::c:: print 'C' if math.random() < 0.1 then goto a else goto b end
另見程式碼產生討論 [8]。
Lua 本身已有 ProperTailRecursion,但如果假設沒有的話,我們可用 goto
模擬尾部呼叫(就像 Lua 的 C 原始碼所做的那樣)
-- 5.2.0-beta-rc2 - factorial with tail recursion simulated with goto's -- (warning: there's no need to do this) function fact_(n, ans) ::call:: if n == 0 then return ans else n, ans = n - 1, ans * n goto call end end print(fact_(5, 1)) --> 120
-- 5.2.0-beta-rc2 function f() if not g() then goto fail end if not h() then goto cleanup_g end if not i() then goto cleanup_h end do return true end -- need do/end? ::cleanup_h:: undo_h() ::cleanup_g:: undo_g() ::fail:: return false
若沒有計算過後的 goto
[4],這是不可能的。另見 SwitchStatement。
標籤名稱若能表示它們會跳躍的方向(向上或向下),可能會讓可讀性變更好。 [10] 在以下範例中,依慣例來說,名稱 continue
和 skip
會跳向下,而名稱 redo
會跳向上。
-- 5.2.0-beta-rc2 ::redo:: for x=1,10 do for y=1,10 do if not f(x,y) then goto continue end if not g(x,y) then goto skip end if not h(x,y) then goto redo end ::continue:: end end ::skip::
如果你在同一個範圍中使用兩個這樣的程式碼片段,你必須分辨出標籤名稱(例如 @redo1:
和 @redo2:
)或在 do/end
區塊中包住它們。
以下是一些關於 goto
範圍規則的範例
::a:: goto b -- valid (forward jump) goto a -- valid (backward jump) ::b:: goto c -- invalid (jump into nested block prohibited because nested label not even visible here) goto d -- invalid (jump into nested function prohibited because nested label not even visible here) do ::c:: goto a -- valid (backward jump out of nested block) goto e -- valid (forward jump out of nested block) end (function() ::d:: goto a -- invalid (jump out of nested function) end)() do ::e:: end -- valid, but not visible outside the block; above "goto e" sees only next line ::e:: -- valid goto f -- invalid (forward jump into scope of local definition) local x ::f:: goto e -- valid (backward jump across local definition) --::e:: -- this would be invalid (duplicate label in same scope)
請注意,你可以認為
do <...> --::a:: goto a -- invalid (forward jump into scope of local definition) goto b -- valid (jump out of block) <...> local x <...> ::a:: <...> --goto a ::b:: end
等於
do <...> --::a:: goto a -- invalid (jump into nested block prohibited because nested label not even visible here) goto b -- valid (jump out of block) <...> do local x <...> ::a:: <...> --goto a end ::b:: end
因此,禁止「跳到區域定義的範圍中」這條規則,在某種程度上包含於禁止「跳到巢狀區塊」的規則(但反之則不然)。然而,5.2.0-beta-rc1 並未將這兩種形式的範圍對待完全類似:如果你在 goto a
之前加上另一個 ::a::
,前一種形式會產生重複標籤的錯誤訊息,但後一種形式不會(雖然在 rc2 中會)因為巢狀 ::a::
對於巢狀區塊外的 goto
永遠都是看不到的(而且巢狀區塊內的任何 goto
都只能看到巢狀的 ::a::
)。
區塊結尾(::b::
)的標籤的特殊對待,就是讓迴圈繼續結構可以被實作(如上方的範例),即使迴圈區塊在繼續之後包含區域變數。
goto
有時可以產生與控制結構完全相同的位元組碼和除錯資訊,不過 for
迴圈除外
-- compare.lua -- tested 5.2.0rc1 local FS = require 'file_slurp' -- https://raw.github.com/gist/1325400/0de9b965af138f2fb3d76fc81d97a863f6f409b3/file_slurp.lua local function compile(code) FS.writefile('luac -o luac.out -', code, 'p') local binary = FS.readfile('luac.out') local text = FS.readfile('./src/luac -p -l luac.out', 'p'):gsub('0x[0-9a-fA-F]+', '(address)') return binary, text end local a, at = compile [[ local x = 1 while not(x > 1e8) do x = x + 1 end ]] local b, bt = compile [[ local x = 1 ::a:: if x > 1e8 then goto e end x = x + 1 goto a; ::e:: ]] assert(a == b) assert(at == bt) local a, at = compile [[ if x then f() else g() end ]] local b, bt = compile [[ if not x then goto a end f() goto b; ::a:: g() ::b:: ]] assert(a == b) assert(at == bt) local a, at = compile [[ local sum = 0 for i=1,1E8 do sum = sum + i end ]] local b, bt = compile [[ local sum = 0 local i=1; ::a:: if i > 1E8 then goto b end sum = sum + i; i=i+1 goto a; ::b:: ]] assert(a ~= b) -- these differ significantly and the latter is about twice as slow. assert(at ~= bt) print 'DONE'
(在較早的 5.2.0beta 中,當條件式區塊內出現一次 goto 時,部分 JMP 是多餘的 [5]).
在 5.2.0-beta-rc1 中,標籤使用語法 @name:
(可選擇加上空白,例如 @ name :
)。重複標籤名稱的限制不同。