第一版是為 Lua 5.0 編寫的。儘管在很大程度上仍然適用於後續版本,但仍有一些區別。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買本書,您還可以幫助 支持 Lua 項目


8 – 編譯、執行和錯誤

儘管我們將 Lua 稱為直譯語言,但 Lua 總會在執行源代碼之前將其預編譯為中間形式。(這不是什麼大問題:大多數直譯語言都這樣做。)在 Lua 這樣的直譯語言中,編譯階段的存在聽起來似乎不合適。然而,直譯語言的顯著特徵並非它們沒有編譯,而是任何編譯器都是語言運行時的一部分,因此,執行動態生成的代碼是可能的(而且容易)。我們可以說,像 dofile 這樣的函數的存在允許 Lua 被稱為直譯語言。

之前,我們將 dofile 作為一種運行 Lua 代碼塊的原始操作。dofile 函數實際上是一個輔助函數;loadfile 執行艱苦的工作。與 dofile 類似,loadfile 也從文件中加載一個 Lua 塊,但它不運行該塊。相反,它只編譯該塊並將編譯後的塊作為函數返回。此外,與 dofile 不同,loadfile 不會引發錯誤,而是返回錯誤代碼,以便我們可以處理錯誤。我們可以定義 dofile 如下

    function dofile (filename)
      local f = assert(loadfile(filename))
      return f()
    end
請注意,如果 loadfile 失敗,則使用 assert 引發錯誤。

對於簡單的任務,dofile 很方便,因為它在一個呼叫中完成整個工作。然而,loadfile 更靈活。如果發生錯誤,loadfile 會傳回 nil 和錯誤訊息,這讓我們可以自訂方式來處理錯誤。此外,如果我們需要執行一個檔案好幾次,我們可以呼叫 loadfile 一次,然後呼叫其結果好幾次。這比呼叫 dofile 好幾次便宜許多,因為程式只會編譯檔案一次。

loadstring 函式類似於 loadfile,除了它從字串讀取區塊,而不是從檔案讀取。例如,在程式碼之後

    f = loadstring("i = i + 1")
f 會是一個函式,當呼叫時,會執行 i = i + 1
    i = 0
    f(); print(i)   --> 1
    f(); print(i)   --> 2
loadstring 函式很強大;必須小心使用。它也是一個昂貴的函式(與其替代方案相比),而且可能會產生難以理解的程式碼。在你使用它之前,請確定沒有更簡單的方法來解決目前的問題。

Lua 將任何獨立區塊視為匿名函式的本體。例如,對於區塊 "a = 1"loadstring 會傳回等同於

    function () a = 1 end
就像任何其他函式一樣,區塊可以宣告局部變數和傳回值
    f = loadstring("local a = 10; return a + 20")
    print(f())          --> 30

loadstringloadfile 都永遠不會引發錯誤。如果發生任何類型的錯誤,這兩個函式都會傳回 nil 和錯誤訊息

    print(loadstring("i i"))
      --> nil     [string "i i"]:1: `=' expected near `i'
此外,這兩個函式永遠不會有任何類型的副作用。它們只會將區塊編譯成內部表示形式,並傳回結果,作為匿名函式。一個常見的錯誤是假設 loadfile(或 loadstring)定義函式。在 Lua 中,函式定義是指定;因此,它們是在執行時間建立,而不是在編譯時間建立。例如,假設我們有一個檔案 foo.lua 如下
    -- file `foo.lua'
    function foo (x)
      print(x)
    end
然後我們執行指令
    f = loadfile("foo.lua")
在此指令之後,foo 已編譯,但尚未定義。若要定義它,你必須執行區塊
    f()           -- defines `foo'
    foo("ok")     --> ok

如果你想執行一個快速且隨意的 dostring(即載入並執行一個區塊),你可以直接呼叫 loadstring 的結果

    loadstring(s)()
然而,如果有任何語法錯誤,loadstring 會傳回 nil,而最後的錯誤訊息會是 "嘗試呼叫 nil 值"。若要取得更清楚的錯誤訊息,請使用 assert
    assert(loadstring(s))()

通常,對文字字串使用 loadstring 沒有意義。例如,程式碼

    f = loadstring("i = i + 1")
大致等同於
    f = function () i = i + 1 end
但第二段程式碼快很多,因為它只在區塊編譯時編譯一次。在第一段程式碼中,每次呼叫 loadstring 都會進行一次新的編譯。然而,這兩段程式碼並不完全等價,因為 loadstring 沒有使用詞彙範圍編譯。為了看出差異,讓我們稍微變更一下先前的範例
    local i = 0
    f = loadstring("i = i + 1")
    g = function () i = i + 1 end
g 函數處理本地的 i,正如預期,但 f 處理全域的 i,因為 loadstring 總是在全域環境中編譯字串。

loadstring 最典型的用途是執行外部程式碼,也就是來自程式外部的程式碼片段。例如,您可能想要繪製使用者定義的函數;使用者輸入函數程式碼,然後您使用 loadstring 來評估它。請注意,loadstring 預期一個區塊,也就是陳述式。如果您想要評估一個表達式,您必須在前面加上 return,這樣您才能取得一個傳回給定表達式值的陳述式。請看範例

    print "enter your expression:"
    local l = io.read()
    local func = assert(loadstring("return " .. l))
    print("the value of your expression is " .. func())

loadstring 傳回的函數是一個常規函數,因此您可以呼叫它多次

    print "enter function to be plotted (with variable `x'):"
    local l = io.read()
    local f = assert(loadstring("return " .. l))
    for i=1,20 do
      x = i   -- global `x' (to be visible from the chunk)
      print(string.rep("*", f()))
    end

在需要執行外部程式碼的生產品質程式中,您應該處理 loadstring 報告的任何錯誤。此外,如果無法信任程式碼,您可能想要在受保護的環境中執行新的區塊,以避免執行程式碼時產生不愉快的副作用。