Goto 语句

lua-users home
wiki

goto 语句在 Lua 5.2.0-beta-rc1 中添加 [6] [1],并在 5.2.0-beta-rc2 中进行了改进 [7]。这是一种限制性的 goto 形式,因为它

本页的其余部分探讨了这种新构造的一些用法。

嵌套 break

-- 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 风格的 redo [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

状态机或马尔可夫链

-- 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 (差异)