48,407
edits
(Created page with "local export = {} local ustring = mw.ustring local libraryUtil = require "libraryUtil" local checkType = libraryUtil.checkType local checkTypeMulti = libraryUtil.checkTypeMul...") |
m (1 revision imported) |
||
| (3 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
local export = {} | local export = {} | ||
local | local debug_track_module = "Module:debug/track" | ||
local | local table_get_unprotected_metatable = "Module:table/getUnprotectedMetatable" | ||
local | local chain -- defined below | ||
local chain_iter -- defined below | |||
local format = string.format | |||
local gmatch = string.gmatch | |||
local ipairs = ipairs | |||
local is_callable -- defined below | |||
local pairs = pairs | |||
local pcall = pcall | |||
local rawget = rawget | |||
local require = require | |||
local select = select | |||
local tostring = tostring | |||
local type = type | |||
local unpack = unpack or table.unpack -- Lua 5.2 compatibility | |||
local unroll -- defined below | |||
local xpcall = xpcall | |||
local function | 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) | |||
i = i + 1 | |||
local char = iter() | |||
if char ~= nil then | |||
return i, char | |||
end | end | ||
end | end | ||
| Line 28: | Line 41: | ||
-- Iterate over UTF-8-encoded codepoints in string. | -- Iterate over UTF-8-encoded codepoints in string. | ||
local function iterString(str) | local function iterString(str) | ||
return _iterString, gmatch(str, ".[\128-\191]*"), 0 | |||
end | |||
local | |||
--[==[ | |||
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). | |||
if | |||
return | 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, allow_maybe) | |||
if type(f) == "function" then | |||
return true | |||
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 = get_unprotected_metatable(f) | |||
if mt == nil then | |||
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 | end | ||
return false | |||
end | 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. | |||
return | 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 | 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 | |||
--[==[ | |||
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 | |||
end | |||
-- If `callable` is nil, there's a protected metatable, so there's no way to check without doing a protected call. | |||
return catch_values(f, pcall(f, ...)) | |||
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 | |||
chain_iter = export.chainIter | |||
do | |||
local function catch_values(start, iter, state, k, ...) | |||
if start == k or k == nil then | |||
return k, ... | |||
end | |||
return catch_values(start, iter, state, iter(state, k)) | |||
end | |||
function export.iterateFrom(start, iter, state, k) | |||
local first = true | |||
return function(state, k) | |||
if first then | |||
first = false | |||
return catch_values(start, iter, state, iter(state, k)) | |||
end | |||
return iter(state, k) | |||
end, state, k | |||
end | |||
end | end | ||
| Line 50: | Line 189: | ||
-- "abc") --> { "A", "B", "C" } | -- "abc") --> { "A", "B", "C" } | ||
function export.map(func, iterable, isArray) | function export.map(func, iterable, isArray) | ||
local array = {} | local array = {} | ||
for k, v in (type(iterable) == "string" and iterString or (isArray or iterable[1] ~= nil) and ipairs or pairs)(iterable) do | |||
array[k] = func(v, k, iterable) | |||
array[ | |||
end | end | ||
return array | return array | ||
end | end | ||
function export.mapIter(func, iter, | function export.mapIter(func, iter, state, init) | ||
-- init could be anything | |||
local array, i = {}, 0 | |||
for x, y in iter, state, init do | |||
-- | |||
local array = {} | |||
for x, y in iter, | |||
i = i + 1 | i = i + 1 | ||
array[i] = func(y, x, | array[i] = func(y, x, state) | ||
end | end | ||
return array | return array | ||
end | |||
do | |||
local function iter_tuples(tuples) | |||
local i = tuples.i | |||
if i > 1 then | |||
i = i - 1 | |||
tuples.i = i | |||
return unpack(tuples[i]) | |||
end | |||
end | |||
-- Takes an iterator function, and returns a new iterator that iterates in reverse, given the same arguments. | |||
-- Note: changes to the state during iteration are not taken into account, since all the return values are calculated in advance. | |||
function export.reverseIter(func) | |||
return function(...) | |||
-- Store all returned values as a list of tuples, then iterate in reverse over that list. | |||
local tuples, i, iter, state, val1 = {}, 0, func(...) | |||
while true do | |||
i = i + 1 | |||
local vals = {iter(state, val1)} | |||
-- Terminates if the first return value is nil, even if other values are non-nil. | |||
val1 = vals[1] | |||
if val1 == nil then | |||
tuples.i = i | |||
return iter_tuples, tuples | |||
end | |||
tuples[i] = vals | |||
end | |||
end | |||
end | |||
end | end | ||
function export.forEach(func, iterable, isArray) | function export.forEach(func, iterable, isArray) | ||
for k, v in (type(iterable) == "string" and iterString or (isArray or iterable[1] ~= nil) and ipairs or pairs)(iterable) do | |||
func(v, k, iterable) | |||
func( | |||
end | end | ||
return nil | return nil | ||
| Line 101: | Line 252: | ||
-- reverse args by building a function to do it, similar to the unpack() example | -- reverse args by building a function to do it, similar to the unpack() example | ||
local function reverseHelper(acc, v, ...) | local function reverseHelper(acc, v, ...) | ||
if select( | if select("#", ...) == 0 then | ||
return v, acc() | return v, acc() | ||
else | else | ||
| Line 141: | Line 292: | ||
-- { 2, 3, 5, 7, 11 }) --> true | -- { 2, 3, 5, 7, 11 }) --> true | ||
function export.some(func, t, isArray) | function export.some(func, t, isArray) | ||
for k, v in ((isArray or t[1] ~= nil) and ipairs or pairs)(t) do | |||
if func(v, k, t) then | |||
return true | |||
end | end | ||
end | end | ||
| Line 160: | Line 303: | ||
-- { 2, 4, 8, 10, 12 }) --> true | -- { 2, 4, 8, 10, 12 }) --> true | ||
function export.all(func, t, isArray) | function export.all(func, t, isArray) | ||
for k, v in ((isArray or t[1] ~= nil) and ipairs or pairs)(t) do | |||
if not func(v, k, t) then | |||
return false | |||
end | end | ||
end | end | ||
| Line 207: | Line 342: | ||
-- Fancy stuff | -- Fancy stuff | ||
local function capture(...) | local function capture(...) | ||
local vals = { n = select( | local vals = {n = select("#", ...), ...} | ||
return function() | return function() | ||
return unpack(vals, 1, vals.n) | return unpack(vals, 1, vals.n) | ||
| Line 235: | 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 | ||
| Line 241: | Line 376: | ||
return t | return t | ||
end | end | ||
return export | return export | ||