使用lua實現try-catch異常捕獲

lua原生並無提供try-catch的語法來捕獲異常處理,可是提供了pcall/xpcall等接口,可在保護模式下執行lua函數。ios

所以,能夠經過封裝這兩個接口,來實現try-catch塊的捕獲機制。git

咱們能夠先來看下,封裝後的try-catch使用方式:github

try
{
    -- try 代碼塊
    function ()
        error("error message")
    end,

    -- catch 代碼塊
    catch 
    {
        -- 發生異常後,被執行
        function (errors)
            print(errors)
        end
    }
}

上面的代碼中,在try塊內部認爲引起了一個異常,而且拋出錯誤消息,在catch中進行了捕獲,而且將錯誤消息進行輸出顯示。api

這裏除了對pcall/xpcall進行了封裝,用來捕獲異常信息,還利用了lua的函數調用語法特性,在只有一個參數傳遞的狀況下,lua能夠直接傳遞一個table類型,而且省略()函數

其實try後面的整個{...} 都是一個table而已,做爲參數傳遞給了try函數,其具體實現以下:ui

function try(block)

    -- get the try function
    local try = block[1]
    assert(try)

    -- get catch and finally functions
    local funcs = block[2]
    if funcs and block[3] then
        table.join2(funcs, block[2])
    end

    -- try to call it
    local ok, errors = pcall(try)
    if not ok then

        -- run the catch function
        if funcs and funcs.catch then
            funcs.catch(errors)
        end
    end

    -- run the finally function
    if funcs and funcs.finally then
        funcs.finally(ok, errors)
    end

    -- ok?
    if ok then
        return errors
    end
end

能夠看到這裏用了pcall來實際調用try塊裏面的函數,這樣就算函數內部出現異常,也不會中斷程序,pcall會返回false表示運行失敗lua

而且返回實際的出錯信息,若是想要自定義格式化錯誤信息,能夠經過調用xpcall來替換pcall,這個接口與pcall相比,多了個錯誤處理函數:插件

local ok, errors = xpcall(try, debug.traceback)

你能夠直接傳入debug.traceback,使用默認的traceback處理接口,也能夠自定義一個處理函數:debug

-- traceback
function my_traceback(errors)

    -- make results
    local level = 2    
    while true do    

        -- get debug info
        local info = debug.getinfo(level, "Sln")

        -- end?
        if not info or (info.name and info.name == "xpcall") then
            break
        end

        -- function?
        if info.what == "C" then
            results = results .. string.format("    [C]: in function '%s'\n", info.name)
        elseif info.name then 
            results = results .. string.format("    [%s:%d]: in function '%s'\n", info.short_src, info.currentline, info.name)    
        elseif info.what == "main" then
            results = results .. string.format("    [%s:%d]: in main chunk\n", info.short_src, info.currentline)    
            break
        else
            results = results .. string.format("    [%s:%d]:\n", info.short_src, info.currentline)    
        end

        -- next
        level = level + 1    
    end    

    -- ok?
    return results
end

-- 調用自定義traceback函數
local ok, errors = xpcall(try, my_traceback)

回到try-catch上來,經過上面的實現,會發現裏面其實還有個finally的處理,這個的做用是對於try{}代碼塊,無論是否執行成功,都會執行到finally塊中code

也就說,其實上面的實現,完整的支持語法是:try-catch-finally模式,其中catch和finally都是可選的,根據本身的實際需求提供

例如:

try
{
    -- try 代碼塊
    function ()
        error("error message")
    end,

    -- catch 代碼塊
    catch 
    {
        -- 發生異常後,被執行
        function (errors)
            print(errors)
        end
    },

    -- finally 代碼塊
    finally 
    {
        -- 最後都會執行到這裏
        function (ok, errors)
            -- 若是try{}中存在異常,ok爲true,errors爲錯誤信息,不然爲false,errors爲try中的返回值
        end
    }
}

或者只有finally塊:

try
{
    -- try 代碼塊
    function ()
        return "info"
    end,

    -- finally 代碼塊
    finally 
    {
        -- 因爲此try代碼沒發生異常,所以ok爲true,errors爲返回值: "info"
        function (ok, errors)
        end
    }
}

處理能夠在finally中獲取try裏面的正常返回值,其實在僅有try的狀況下,也是能夠獲取返回值的:

-- 若是沒發生異常,result 爲返回值:"xxxx",不然爲nil
local result = try
{
    function ()
        return "xxxx"
    end
}

能夠看到,這個基於pcall的try-catch-finally異常捕獲封裝,使用上仍是很是靈活的,並且其實現至關的簡單

這也充分說明了lua是一門已很是強大靈活,又很是精簡的語言。

xmake的自定義腳本、插件開發中,也是徹底基於此異常捕獲機制

這樣使得擴展腳本的開發很是的精簡可讀,省去了繁瑣的if err ~= nil then返回值判斷,在發生錯誤時,xmake會直接拋出異常進行中斷,而後高亮提示詳細的錯誤信息。

例如:

target("test")
    set_kind("binary")
    add_files("src/*.c")

    -- 在編譯完ios程序後,對目標程序進行ldid簽名
    after_build(function (target))
        os.run("ldid -S %s", target:targetfile())
    end

只須要一行os.run就好了,也不須要返回值判斷是否運行成功,由於運行失敗後,xmake會自動拋異常,中斷程序而且提示錯誤

若是你想在運行失敗後,不直接中斷xmake,繼續往下運行,能夠本身加個try快就好了:

target("test")
    set_kind("binary")
    add_files("src/*.c")

    after_build(function (target))
        try
        {
            function ()
                os.run("ldid -S %s", target:targetfile())
            end
        }
    end

若是還想捕獲出錯信息,能夠再加個catch:

target("test")
    set_kind("binary")
    add_files("src/*.c")

    after_build(function (target))
        try
        {
            function ()
                os.run("ldid -S %s", target:targetfile())
            end,
            catch 
            {
                function (errors)
                    print(errors)
                end
            }
        }
    end

不過通常狀況下,在xmake中寫自定義腳本,是不須要手動加try-catch的,直接調用各類api,出錯後讓xmake默認的處理程序接管,直接中斷就好了。。

最後附上try-catch-finally實現的相關完整源碼:


我的主頁:TBOOX開源工程

相關文章
相關標籤/搜索