Use lua to achieve try-catch exception capture

Lua does not natively provide try-catch syntax to capture exception handling, but provides an pcall/xpcallinterface for executing Lua functions in protected mode.

Therefore, the capture mechanism of the try-catch block can be realized by encapsulating these two interfaces.

We can first look at the use of try-catch after encapsulation:

try
{
    -- try 代码块
    function ()
        error("error message")
    end,

    -- catch 代码块
    catch 
    {
        -- 发生异常后,被执行
        function (errors)
            print(errors)
        end
    }
}

In the above code, an exception is considered in the try block, and an error message is thrown, captured in the catch, and the error message is output and displayed.

In addition to pcall/xpcallencapsulation, it is used to capture exception information. It also uses the function call syntax feature of Lua. In the case of only one parameter passing, Lua can directly pass a table type and omit it.()

In fact, the whole behind try {...}is just a table, which is passed to the try function as a parameter, and its specific implementation is as follows:

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

You can see that it is used pcallto actually call the function in the try block, so that even if there is an exception inside the function, the program pcallwill not be interrupted, and false will be returned to indicate a failure.

And return the actual error message. If you want to customize the formatting error message, you can replace pcall by calling xpcall. Compared with pcall, this interface has an additional error handling function:

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

You can pass debug.tracebackin directly , use the default traceback processing interface, or customize a processing function:

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

Going back to try-catch, through the above implementation, you will find that there is actually a finally processing inside. This function is for the try{}code block, regardless of whether the execution is successful or not, it will be executed in the finally block

In other words, in fact, the above implementation, the complete support syntax is: try-catch-finallymode, in which catch and finally are both optional, provided according to their actual needs

E.g:

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

Or only finally block:

try
{
    -- try 代码块
    function ()
        return "info"
    end,

    -- finally 代码块
    finally 
    {
        -- 由于此try代码没发生异常,因此ok为true,errors为返回值: "info"
        function (ok, errors)
        end
    }
}

Processing can obtain the normal return value in try in finally, in fact, in the case of only try, the return value can also be obtained:

-- 如果没发生异常,result 为返回值:"xxxx",否则为nil
local result = try
{
    function ()
        return "xxxx"
    end
}

As you can see, this pcall-based try-catch-finallyexception capture package is still very flexible in use, and its implementation is quite simple

This also fully shows that Lua is a very powerful, flexible, and very streamlined language.

In the development of custom scripts and plug-ins of xmake , it is also completely based on this exception capture mechanism

This makes the development of extended scripts very concise and readable, eliminating the need for tedious if err ~= nil thenreturn value judgments. When an error occurs, xmake will directly throw an exception to interrupt, and then highlight the detailed error message.

E.g:

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

    -- 在编译完ios程序后,对目标程序进行ldid签名
    after_build(function (target))
        os.run("ldid -S %s", target:targetfile())
    end

Only one line os.runis needed, and there is no need to return a value to judge whether the operation is successful, because after the operation fails, xmake will automatically throw an exception, interrupt the program and prompt an error

If you want to continue running without directly interrupting xmake after the operation fails, you can add a try yourself:

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

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

If you want to capture the error message, you can add another 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

However, under normal circumstances, writing custom scripts in xmake does not need to manually add try-catch, directly call various apis, let xmake's default handler take over after an error, and just interrupt it directly. .

Finally try-catch-finally, the relevant complete source code of the implementation is attached :


Personal homepage: TBOOX open source project
Original source: http://tboox.org/cn/2016/12/14/try-catch/

Guess you like

Origin blog.csdn.net/waruqi/article/details/53649634
Recommended