Goto 語法

lua-users home
Wiki

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

Perl 風格重做 [2]

-- 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

Pythonic for-else [3]

-- 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

狀態機或 Markov 鏈條

-- 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
(出自 [9]

Switch 語句

若沒有計算過後的 goto [4],這是不可能的。另見 SwitchStatement

標籤慣例

標籤名稱若能表示它們會跳躍的方向(向上或向下),可能會讓可讀性變更好。 [10] 在以下範例中,依慣例來說,名稱 continueskip 會跳向下,而名稱 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 :)。重複標籤名稱的限制不同。


最近變更 · 偏好設定
編輯 · 歷史記錄
最後編輯時間 2014 年 2 月 2 日 上午 9:27 GMT (diff)