Module:fun: Difference between revisions
Jump to navigation
Jump to search
m
(bot) slight optimization to 5.2 compat: prefer unpack to table.unpack
No edit summary |
m ((bot) slight optimization to 5.2 compat: prefer unpack to table.unpack) |
||
| Line 1: | Line 1: | ||
local export = {} | local export = {} | ||
local | local debug_track_module = "Module:debug/track" | ||
local table_get_unprotected_metatable = "Module:table/getUnprotectedMetatable" | |||
local chain -- defined below | |||
local chain_iter -- defined below | |||
local format = string.format | |||
local gmatch = string.gmatch | local gmatch = string.gmatch | ||
local ipairs = ipairs | local ipairs = ipairs | ||
local is_callable -- defined below | |||
local pairs = pairs | local pairs = pairs | ||
local pcall = pcall | local pcall = pcall | ||
local rawget = rawget | local rawget = rawget | ||
local require = require | |||
local select = select | local select = select | ||
local tostring = tostring | local tostring = tostring | ||
local type = type | local type = type | ||
local unpack = unpack | local unpack = unpack or table.unpack -- Lua 5.2 compatibility | ||
local unroll -- defined below | |||
local xpcall = xpcall | |||
local function debug_track(...) | |||
debug_track = require(debug_track_module) | |||
return debug_track(...) | |||
end | |||
local function get_unprotected_metatable(...) | |||
get_unprotected_metatable = require(table_get_unprotected_metatable) | |||
return get_unprotected_metatable(...) | |||
end | |||
local function _iterString(iter, i) | local function _iterString(iter, i) | ||
| Line 31: | Line 45: | ||
--[==[ | --[==[ | ||
Return {true} if the input is a function or functor ( | Return {true} if the input is a function or functor (an object which can be called like a function, because it has a {__call} metamethod). | ||
Note: if the input is | Note: if the input is an object with a {__call} metamethod, but this function is not able to find it because the object's metatable is protected with {__metatable}, then it will return {false} by default, or {nil} if the {allow_maybe} flag is set.]==] | ||
function export.is_callable(f) | function export.is_callable(f, allow_maybe) | ||
if type(f) == "function" then | |||
return true | return true | ||
end | end | ||
-- | -- An object is a functor if it has a `__call` metamethod. The only way to truly confirm this is by trying to call it, but that could be expensive or have side effects, so look for a `__call` metamethod instead. If the metatable is protected with `__metatable`, this may not be possible. | ||
local mt = | local mt = get_unprotected_metatable(f) | ||
if mt == nil then | if mt == nil then | ||
return false | return false | ||
-- `get_unprotected_metatable` returns false if the metatable is protected. | |||
elseif mt == false then | |||
debug_track("fun/is_callable/protected metatable") | |||
if allow_maybe then | |||
return nil | |||
end | |||
return false | |||
end | |||
-- `__call` metamethods have to be functions, so don't recurse to check it. | |||
local __call = rawget(mt, "__call") | |||
return __call and type(__call) == "function" or false | |||
end | |||
is_callable = export.is_callable | |||
--[==[ | |||
A version of {xpcall} which takes any arguments to be given to {f} as additional arguments after the error handler. | |||
This fixes a deficiency in the standard version of {xpcall}, which is not able to handle arguments to be given to {f}, and brings it in line with {pcall}.]==] | |||
function export.xpcall(f, err_handler, ...) | |||
-- If there are no arguments, just call xpcall() with `f`. | |||
if select("#", ...) == 0 then | |||
return xpcall(f, err_handler) | |||
end | |||
-- Any arguments have to be smuggled in via a table, as ... can't be an | |||
-- upvalue, and it's not possible to use pcall() to get aroud this, because | |||
-- xpcall() calls the error handler before the stack unwinds. | |||
local args = {...} | |||
return xpcall(function() | |||
return f(unpack(args)) | |||
end, err_handler) | |||
end | |||
do | |||
local function catch_values(f, success, ...) | |||
if success then | |||
return success, ... | |||
-- Error message will only take this exact form if `f` is not callable, | |||
-- because it will contain a traceback if it was thrown further up the | |||
-- stack. | |||
elseif (...) == format("attempt to call a %s value", type(f)) then | |||
return false | |||
end | |||
return error(...) | |||
end | end | ||
-- | |||
--[==[ | |||
A special form of {pcall()}, which returns {true} plus the result value(s) if {f} is callable, or {false} if it isn't. Errors that occur within the called function are not protected.]==] | |||
function export.try_call(f, ...) | |||
local callable = is_callable(f, true) | |||
if callable then | |||
return true, f(...) | |||
elseif callable == false then | |||
return false | return false | ||
end | end | ||
-- If `callable` is nil, there's a protected metatable, so there's no way to check without doing a protected call. | |||
-- If | return catch_values(f, pcall(f, ...)) | ||
end | end | ||
end | end | ||
--[==[ | |||
Takes two or more functions as arguments, and returns a new function which calls each of the input functions in turn. Any arguments given to the returned function are given to the first function, and all other functions receive the output value(s) from the previous function.]==] | |||
function export.chain(func1, func2, ...) | function export.chain(func1, func2, ...) | ||
return func1( | local function chained_func(...) | ||
return func2(func1(...)) | |||
end | |||
if select("#", ...) == 0 then | |||
return chained_func | |||
end | |||
return chain(chained_func, ...) | |||
end | |||
chain = export.chain | |||
--[==[ | |||
Takes the usual for-loop parameters (an iterator, plus an optional state and initial index), and unrolls the iterator by returning every (first) value returned by the iterator. | |||
For instance, {unroll(pairs(t))} will return every key in {t}, and {unroll(string.gmatch(s, "%w+"))} will return every word in {s}.]==] | |||
function export.unroll(iter, state, k) | |||
k = iter(state, k) | |||
if k ~= nil then | |||
return k, unroll(iter, state, k) | |||
end | |||
end | |||
unroll = export.unroll | |||
--[==[ | |||
Takes a generator function (i.e. a function that returns an iterator, such as {ipairs}) and one or more additional functions, and returns a new generator function. Any arguments given to the new generator (e.g. an input table) are given to the original generator, and the additional functions are called on each iteration. The first additional function takes the output from the original iterator (i.e. the function returned by the original generator), and any further functions receive the output value(s) from the previous function. This can be used to modify the values returned from an iterator.]==] | |||
function export.chainIter(gen, new_iter, ...) | |||
if select("#", ...) > 0 then | |||
new_iter = chain(new_iter, ...) | |||
end | |||
return function(...) | |||
local orig_iter, state, k = gen(...) | |||
-- k has to be the first value returned by orig_iter on the last iteration, not whatever new_iter returned. | |||
local function catch_values(...) | |||
k = ... | |||
if k ~= nil then | |||
return new_iter(...) | |||
end | |||
end | |||
return function() | |||
return catch_values(orig_iter(state, k)) | |||
end, state, k | |||
end | |||
end | end | ||
chain_iter = export.chainIter | |||
do | do | ||
| Line 272: | Line 370: | ||
function export.logAll(t) | function export.logAll(t) | ||
for k, v in pairs(t) do | for k, v in pairs(t) do | ||
if | if is_callable(v) then | ||
t[k] = export.logReturnValues(v, tostring(k)) | t[k] = export.logReturnValues(v, tostring(k)) | ||
end | end | ||