《Programming in Lua中文版》 — 8.Compilation, Execution, and Errors

來源:互聯網
上載者:User
《Programming in Lua中文版》 -- 8.Compilation, Execution, and Errors
轉自http://www.lifangjin.com/ (涼風集    李方進的個人BLOG)

雖然我們把Lua當作解釋型語言,但是Lua會首先把代碼先行編譯成中間碼然後再執行(很多解釋型語言都是這麼做的).在解釋型語言中存在編譯階段聽 起來不合適,然而,解釋型語言的特徵不在於他們是否被編譯,而是編譯器是語言運行時的一部分,所以,執行編譯產生的中間碼速度會更快.我們可以說函數 dofile的存在就是說明可以將Lua作為一種解釋型語言被調用.
前面我們介紹過dofile,把它當作Lua運行代碼的chunk的一種原始的操作.dofile實際上是一個輔助的函數,真正完成功能的函數是 loadfile;與dofile不同的是loadfile編譯代碼成中間碼並且返回編譯後的chunk作為一個函數,而不執行代碼;另外 loadfile不會拋出錯誤資訊而是返回錯誤碼.我們可以這樣定義dofile:
function dofile (filename)
local f = assert(loadfile(filename))
return f()
end
如果loadfile失敗assert會拋出錯誤.
完成簡單的功能dofile比較方便,他讀入檔案編譯並且執行.然而loadfile更加靈活.在發生錯誤的情況下,loadfile返回nil和錯誤信 息,這樣我們就可以自訂錯誤處理.另外,如果我們運行一個檔案多次的話,loadfile只需要編譯一次,但可多次運行.dofile卻每次都要編譯.
loadstring與loadfile相似,只不過它不是從檔案裡讀入chunk,而是從一個串中讀入.例如:
f = loadstring(”i = i + 1″)
f將是一個函數,調用時執行:i=i+1:
i = 0
f(); print(i) –> 1
f(); print(i) –> 2
loadstring函數功能強大,但使用時需多加小心.確認沒有其它簡單的解決問題的方法再使用.
Lua把每一個chunk都作為一個匿名函數處理.例如:chunk “a = 1″,loadstring返回與其等價的function () a = 1 end
與其他函數一樣,chunks可以定義局部變數也可以傳回值:
f = loadstring(”local a = 10; return a + 20″)
print(f()) –> 30
loadfile和loadstring都不會拋出錯誤,如果發生錯誤他們將返回nil加上錯誤資訊:
print(loadstring(”i i”))
–> nil [string “i i”]:1: `=’ expected near `i’

另外,loadfile和loadstring都不會有邊界效應產生,他們僅僅編譯chunk成為自己內部實現的一個匿名函數.通常對他們的誤解是他們定 義了函數.Lua中的函數定義是發生在運行時的賦值而不是發生在編譯時間.假如我們有一個檔案foo.lua:
— file `foo.lua’
function foo (x)
print(x)
end
當我們執行命令f = loadfile(”foo.lua”)後,foo被編譯了但還沒有被定義,如果要定義他必須運行chunk:
f() — defines `foo’
foo(”ok”) –> ok
如果你想快捷的調用dostring (比如載入並運行)可以調用loadstring 返回的結果loadstring(s)(),然而如果載入的內容存在語法錯誤的話,loadstring返回nil和錯誤資訊(attempt to call a nil value);為了返回更清楚的錯誤資訊可以使用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期望一個chunk,即語句.如果想要載入運算式,需要在運算式前加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
8.1 require函數
Lua提供進階的require函數來載入運行庫.粗略的說require和dofile完成同樣的功能但有兩點不同:1.require會搜尋目錄載入 檔案2.require會判斷是否檔案已經載入避免重複載入同一檔案.由於上述特徵,require在Lua中是載入庫的更好的函數.
require使用的路徑和普通我們看到的路徑還有些區別,我們一般見到的路徑都是一個目錄列表.require的路徑是一個模式列表,每一個模式指明一 種由虛檔案名稱(require的參數)轉成實檔案名稱的方法.更明確地說,每一個模式是一個包含可選的問號的檔案名稱.匹配的時候Lua會首先將問號用虛檔案 名替換,然後看是否有這樣的檔案存在.如果不存在繼續用同樣的方法用第二個模式比對.例如,路徑如下:
?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
調用require”lili”時會試著開啟這些檔案:
lili
lili.lua
c:\windows\lili
/usr/local/lua/lili/lili.lua
require關注的問題只有分號(模式之間的分隔字元)和問號,其他的資訊(目錄分隔字元,副檔名)在路徑中定義.
為了確定路徑,Lua首先檢查全域變數LUA_PATH是否為一個字串,如果是則認為這個串就是路徑;否則require檢查環境變數LUA_PATH的值,如果兩個都失敗require使用固定的路徑(典型的 “?;?.lua”)
require的另一個功能是避免重複載入同一個檔案兩次.Lua保留一張所有已經載入的檔案的列表(使用table儲存).如果一個載入的檔案在表中存 在require簡單的返回;表中保留載入的檔案的虛名,而不是實檔案名稱.所以如果你使用不同的虛檔案名稱require同一個檔案兩次,將會載入兩次該文 件.比如require “foo”和require “foo.lua”,路徑為”?;?.lua”將會載入foo.lua兩次.我們也可以通過全域變數_LOADED訪問檔案名稱列表,這樣我們就可以判斷文 件是否被載入過;同樣我們也可以使用一點小技巧讓require載入一個檔案兩次.比如,require “foo”之後_LOADED[”foo”]將不為nil,我們可以將其賦值為nil,require “foo.lua”將會再次載入該檔案.
一個路徑中的模式也可以不包含問號而只是一個固定的路徑,比如:
?;?.lua;/usr/local/default.lua
這種情況下,require沒有匹配的時候就會使用這個固定的檔案(當然這個固定的路徑必須放在模式列表的最後才有意義).在require運行一個 chunk以前,它定義了一個全域變數_REQUIREDNAME用來儲存被required的虛檔案的檔案名稱.我們可以通過使用這個技巧擴充 require的功能.舉個極端的例子,我們可以把路徑設為”/usr/local/lua/newrequire.lua”,這樣以後每次調用 require都會運行newrequire.lua,這種情況下可以通過使用_REQUIREDNAME 的值去實際載入required的檔案.
8.2 C-包
Lua和C是很容易結合的,使用C為Lua寫包.與Lua中寫包不同,C包在使用以前必須首先載入並串連.在大多數系統中最容易的實現方式是通過動態串連庫機制,然而動態串連庫不是ANSI C的一部分,也就是說在標準C中實現動態串連是很困難的.
通常Lua不包含任何不能用標準C實現的機制,動態串連庫是一個特例.我們可以將動態串連庫機制視為其他機制之母:一旦我們擁有了動態串連機制,我們就可 以動態載入Lua中不存在的機制.所以,在這種特殊情況下,Lua打破了他平台相容的原則而通過條件編譯的方式為一些平台實現了動態串連機制.標準的 Lua為windows,Linux,FreeBSD,Solaris和其他一些Unix平台實現了這種機制,擴充其它平台支援這種機制也是不難的.在 Lua提示符下運行print(loadlib())看返回的結果如果顯示bad arguments則說明你的發布版支援動態串連機制,否則說明動態串連機制不支援或者沒有安裝.
Lua在一個叫loadlib的函數內提供了所有的動態串連的功能.這個函數有兩個參數:庫的絕對路徑和初始化函數.所以典型的調用的例子如下:
local path = “/usr/local/lua/lib/libluasocket.so”
local f = loadlib(path, “luaopen_socket”)
loadlib函數載入指定的庫並且串連到Lua,然而它並不開啟庫(也就是說沒有調用初始化函數),反之他返回初始化函數作為Lua的一個函數,這樣我 們就可以直接在Lua中調用他.如果載入動態庫或者尋找初始化函數時出錯,loadlib將返回nil和錯誤資訊.我們可以修改前面一段代碼,使其檢測錯 誤然後調用初始化函數:
local path = “/usr/local/lua/lib/libluasocket.so”
— or path = “C:\\windows\\luasocket.dll”
local f = assert(loadlib(path, “luaopen_socket”))
f() — actually open the library
一般情況下我們期望二進位的發布庫包含一個與前面程式碼片段相似的stub檔案,安裝二進位庫的時候可以隨便放在某個目錄,只需要修改stub檔案對應二進位 庫的實際路徑即可.將stub檔案所在的目錄加入到LUA_PATH,這樣設定後就可以使用require函數載入C庫了.
8.3 錯誤
Errare humanum est(拉丁諺語:犯錯是人的本性).所以我們要儘可能的防止錯誤的發生,Lua經常作為擴充語言嵌入在別的應用中,所以不能當錯誤發生時簡單的崩潰或者退出.相反,當錯誤發生時Lua結束當前的chunk並返回到應用中.
當Lua遇到不期望的情況時就會拋出錯誤,比如:兩個非數字進行相加;調用一個非函數的變數;訪問表中不存在的值等(可以通過metatables修改這 種行為,後面介紹).你也可以通過調用error函數顯示的拋出錯誤,error的參數是要拋出的錯誤資訊.
print “enter a number:”
n = io.read(”*number”)
if not n then error(”invalid input”) end
Lua提供了專門的內建函數assert來完成上面類似的功能:
print “enter a number:”
n = assert(io.read(”*number”), “invalid input”)
assert首先檢查第一個參數是否返回錯誤,如果不返回錯誤assert簡單的返回,否則assert以第二個參數拋出錯誤資訊.第二個參數是可選的.注意assert是普通的函數,他會首先計算兩個參數然後再調用函數,所以以下代碼:
n = io.read()
assert(tonumber(n),
“invalid input: ” .. n .. ” is not a number”)
將會總是進行串連操作,使用顯示的test可以避免這種情況.
當函數遇到異常有兩個基本的動作:返回錯誤碼或者拋出錯誤.這兩種方式選擇哪一種沒有固定的規則,但有一般的原則:容易避免的異常應該拋出錯誤否則返回錯誤碼.
例如我們考慮sin函數,如果以一個table作為參數,假定我們返回錯誤碼,我們需要檢查錯誤的發生,代碼可能如下:
local res = math.sin(x)
if not res then — error
…
然而我們可以在調用函數以前很容易的判斷是否有異常:
if not tonumber(x) then — error: x is not a number
…
然而通常情況下我們既不是檢查參數也不是檢查返回結果,因為參數錯誤可能意味著我們的程式某個地方存在問題,這種情況下,處理異常最簡單最實際的方式是拋出錯誤並且終止代碼的運行.
再來看一個例子io.open函數用來開啟一個檔案,如果檔案不存在結果會怎麼樣呢?很多系統中,通過試著去開啟檔案來判斷是否檔案存在.所以如果 io.open不能開啟檔案(由於檔案不存在或者沒有許可權),函數返回nil和錯誤資訊.以這種方式我們可以通過與使用者互動(比如:是否要開啟另一個文 件)合理的處理問題:
local file, msg
repeat
print “enter a file name:”
local name = io.read()
if not name then return end — no input
file, msg = io.open(name, “r”)
if not file then print(msg) end
until file
如果你想偷懶不想處理這些情況,又想代碼安全的運行,可以簡單的使用assert:
file = assert(io.open(name, “r”))
Lua中有一個習慣:如果io.open失敗,assert將拋出錯誤.
file = assert(io.open(”no-file”, “r”))
–> stdin:1: no-file: No such file or directory
注意:io.open返回的第二個結果(錯誤資訊)作為assert的第二個參數.
8.4 異常和錯誤處理
很多應用中,不需要在Lua進行錯誤處理,一般有應用來完成.通常應用要求Lua運行一段chunk,如果發生異常,應用根據Lua返回的錯誤碼進行處理.在控制台模式下的Lua解譯器如果遇到異常,列印出錯誤然後繼續顯示提示符等待下一個命令.
如果在Lua中需要處理錯誤,需要使用pcall函數封裝你的代碼.
假定你想運行一段Lua代碼,這段代碼運行過程中可以捕捉所有的異常和錯誤,第一步:將這段代碼封裝在一個函數內,
function foo ()
…
if unexpected_condition then error() end
…
print(a[i]) — potential error: `a’ may not be a table
…
end
第二步:使用pcall調用這個函數
if pcall(foo) then
— no errors while running `foo’
…
else
— `foo’ raised an error: take appropriate actions
…
end
當然也可以用匿名函數的方式調用pcall:
if pcall(function () … end) then …
else …
pcall在保護模式下調用他的第一個參數並運行,因此可以捕獲所有的異常和錯誤.如果沒有異常和錯誤,pcall返回true和調用返回的任何值;否則返回nil加錯誤資訊.
錯誤資訊不一定非要是一個字串(下面的例子是一個table),傳遞給error的任何資訊都會被pcall返回:
local status, err = pcall(function () error({code=121}) end)
print(err.code) –> 121
這種機制提供了我們在Lua中處理異常和錯誤的所需要的全部內容.我們通過error拋出異常,然後通過pcall捕獲他.
8.5 錯誤資訊和回跟蹤(Tracebacks)
雖然你可以使用任何類型的值作為錯誤資訊,通常情況下,我們使用字串來描述遇到的錯誤資訊.如果遇到內部錯誤(比如對一個非table的值使用索引下表 訪問)Lua將自己產生錯誤資訊,否則Lua使用傳遞給error函數的參數作為錯誤資訊.不管在什麼情況下,Lua都儘可能清楚的描述發生的錯誤.
local status, err = pcall(function () a = ‘a’+1 end)
print(err)
–> stdin:1: attempt to perform arithmetic on a string value

local status, err = pcall(function () error(”my error”) end)
print(err)
–> stdin:1: my error
例子中錯誤資訊給出了檔案名稱(stdin)加上行號.
函數error還可以有第二個參數,表示錯誤的運行層級.有了這個參數你就無法抵賴錯誤是別人的了,比如,加入你寫了一個函數用來檢查error是否被正確的調用:
function foo (str)
if type(str) ~= “string” then
error(”string expected”)
end
…
end
可能有人這樣調用這個函數:
foo({x=1})
Lua會指出發生錯誤的是foo而不是error,實際的錯誤是調用error時產生的,為了糾正這個問題修改前面的代碼讓error報告錯誤發生在第二級(你自己的函數是第一級)如下:
function foo (str)
if type(str) ~= “string” then
error(”string expected”, 2)
end
…
end
當錯誤發生的時候我們常常需要更多的錯誤發生相關的資訊,而不單單是錯誤發生的位置.至少期望有一個完整的顯示導致錯誤發生的調用棧的 tracebacks,當pcall返回錯誤資訊的時候他已經釋放了儲存錯誤發生情況的棧的資訊.因此,如果我們想得到tracebacks我們必須在 pcall返回以前擷取.Lua提供了xpcall 來實現這個功能,xpcall接受兩個參數:調用函數和錯誤處理函數.當錯誤發生時,Lua會在棧釋放以前調用錯誤處理函數,因此可以使用debug庫收 集錯誤相關的資訊.有兩個常用的debug處理函數:debug.debug和debug.traceback,前者給出Lua的提示符,你可以自己動手 察看錯誤發生時的情況;後者通過traceback建立更多的錯誤資訊,後者是控制台解譯器用來構建錯誤資訊的函數.你可以在任何時候調用 debug.traceback擷取當前啟動並執行traceback 資訊:
print(debug.traceback())

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.