Last active
August 27, 2024 20:49
-
-
Save johnd0e/17918a5199aaecb1747f3b2c650322ce to your computer and use it in GitHub Desktop.
Exception control - lua implementation of ltn13 (from luasocket)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- idea by Diego Nehab: https://github.com/lunarmodules/luasocket/blob/master/ltn013.md | |
-- improved by Peter Melnichenko: https://github.com/lunarmodules/luasocket/pull/161/files | |
-- https://github.com/mpeterv/luasocket/blob/e7b68bb49ce1a40effadda48969b9377e30887cb/src/except.lua | |
-- https://github.com/mpeterv/luasocket/blob/e7b68bb49ce1a40effadda48969b9377e30887cb/test/excepttest.lua | |
-- With some changes from John Doe: | |
-- - finalizer's call is unprotected, like in luasocket | |
-- - `except.try`, like in luasocket | |
-- - `except.newtry` demands a finalizer | |
-- - argument for finalizer can be specified | |
-- - on error `except.protect` returns the exact return values (instead of just `nil, arg[2]`) | |
-- - new `except.call` is same as `pcall` but does not return first `success` value if it is `true`. | |
----------------------------------------------------------------------------- | |
-- Exception control | |
-- LuaSocket toolkit (but completely independent from other modules) | |
-- Author: Diego Nehab | |
-- This provides support for simple exceptions in Lua. During the | |
-- development of the HTTP/FTP/SMTP support, it became aparent that | |
-- error checking was taking a substantial amount of the coding. These | |
-- function greatly simplify the task of checking errors. | |
-- The main idea is that functions should return nil as its first return | |
-- value when it finds an error, and return an error message (or value) | |
-- following nil. In case of success, as long as the first value is not nil, | |
-- the other values don't matter. | |
-- The idea is to nest function calls with the "try" function. This function | |
-- checks the first value, and, if it's falsy, wraps the second value | |
-- in a table with metatable and calls "error" on it. Otherwise, | |
-- it returns all values it received. | |
-- The "newtry" function is a factory for "try" functions that call a finalizer | |
-- in protected mode before calling "error". | |
-- The "protect" function returns a new function that behaves exactly like the | |
-- function it receives, but the new function catches exceptions | |
-- thrown by "try" functions and returns nil followed by the error message | |
-- instead. | |
-- With these three function, it's easy to write functions that throw | |
-- exceptions on error, but that don't interrupt the user script. | |
----------------------------------------------------------------------------- | |
local base = _G | |
local _M = {} | |
local exception_metat = {} | |
function exception_metat:__tostring() | |
return base.tostring(self[1]) | |
end | |
local function do_nothing() end | |
function _M.newtry(finalizer, arg) | |
assert(type(finalizer)=="function", "finalizer function expected") | |
return function(ok, ...) | |
if ok then | |
return ok, ... | |
else | |
finalizer(arg) | |
base.error(base.setmetatable({ | |
n=select("#", ...), [0]=ok, ... | |
}, exception_metat)) | |
end | |
end | |
end | |
local function handle_pcall_returns(ok, ...) | |
if ok then | |
return ... | |
else | |
local err = ... | |
if base.getmetatable(err) == exception_metat then | |
return unpack(err, 0, err.n) | |
else | |
base.error(err, 0) | |
end | |
end | |
end | |
function _M.protect(func) | |
return function(...) | |
return handle_pcall_returns(base.pcall(func, ...)) | |
end | |
end | |
_M.try = _M.newtry(do_nothing) | |
local function shift (...) | |
if ... then | |
return base.select(2,...) | |
end | |
return ... | |
end | |
function _M.call (...) | |
return shift(base.pcall(...)) | |
end | |
if ...=="test" then | |
return _M | |
end | |
------------------------------------- | |
local except = _M -- require "except" | |
local finalizer_called | |
local func = except.protect(function(err, ...) | |
local try = except.newtry(function() | |
finalizer_called = true | |
end) | |
if err then | |
return error(err, 0) | |
else | |
return try(...) | |
end | |
end) | |
local ret1, ret2, ret3 = func(false, 1, 2, 3) | |
assert(not finalizer_called, "unexpected finalizer call") | |
assert(ret1 == 1 and ret2 == 2 and ret3 == 3, "incorrect return values") | |
ret1, ret2, ret3 = func(false, false, "error message") | |
assert(finalizer_called, "finalizer not called") | |
assert(ret1 == false and ret2 == "error message" and ret3 == nil, "incorrect return values") | |
local err = {key = "value"} | |
ret1, ret2 = pcall(func, err) | |
assert(not ret1, "error not rethrown") | |
assert(ret2 == err, "incorrect error rethrown") | |
-- JD | |
assert(pcall(except.try,1,2,3), "try: should not throw on true value") | |
ret1, ret2, ret3 = except.try(1,2,3) | |
assert(ret1 == 1 and ret2 == 2 and ret3 == 3, "try: incorrect return values") | |
func = except.protect(function(...) | |
except.try(...) | |
return "success" | |
end) | |
ret1, ret2, ret3 = func(false, 2, 3) | |
assert(ret1 == false and ret2 == 2 and ret3 == 3, "incorrect return values") | |
func = except.protect(function(finalizer, ...) | |
local try = except.newtry(finalizer, "finalizer_arg") | |
try(...) | |
return "success" | |
end) | |
assert(not pcall(func, error, nil), "protect: should propagate error in finalizer") | |
local finalizer_arg | |
func(function (arg) finalizer_arg = arg end, nil) | |
assert(finalizer_arg=="finalizer_arg", "argument should be passed to finalizer") | |
local success, errmsg = pcall(func) | |
assert(not success and errmsg:match("finalizer function expected$"), "newtry: finalizer must be specified") | |
ret1, ret2, ret3 = func(do_nothing, nil,2,3) | |
assert(ret1==nil and ret2==2 and ret3==3, "protect: on error all the values must be returned as they are") | |
local ret1p, ret2p, ret3p, _ | |
ret1p, ret2p = pcall(error, "Error") | |
ret1, ret2 =except.call(error, "Error") | |
assert(ret1==ret1p and ret2==ret2p, "call: on error behaves exactly like pcall") | |
_, ret2p, ret3p = pcall(function() return 1,2 end) | |
ret1, ret2 =except.call(function() return 1,2 end) | |
assert(ret1==ret2p and ret2==ret3p, "call: on success returns only func return value") | |
print("OK") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment