45,645
edits
No edit summary |
No edit summary |
||
Line 75: | Line 75: | ||
--[[ | --[[ | ||
------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ||
-- | -- shallowcopy | ||
-- | -- | ||
-- This returns a clone of a table | -- This returns a clone of an object. If the object is a table, the value | ||
-- returned is a new table, but all subtables and functions are shared. | |||
-- Metamethods are respected, but the returned table will have no metatable of | |||
-- its own. | |||
------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ||
--]] | --]] | ||
function export.shallowcopy(orig) | function export.shallowcopy(orig) | ||
local orig_type = type(orig) | local orig_type = type(orig) | ||
Line 107: | Line 97: | ||
end | end | ||
-- | -- An alias for shallowcopy(); prefer shallowcopy(). | ||
Recursive deep copy function | function export.shallowClone(t) | ||
return export.shallowcopy(t) | |||
end | |||
------------------------------------------------------------------------------------ | |||
already_seen = | -- deepcopy | ||
-- | |||
-- Recursive deep copy function. Preserves copied identities of subtables. | |||
-- A more powerful version of mw.clone, as it is able to clone recursive tables without getting into an infinite loop. | |||
-- NOTE: protected metatables will not be copied (i.e. those hidden behind a __metatable metamethod), as they are not accessible by Lua's design. Instead, the output of the __metatable method will be used instead. | |||
-- An exception is made for data loaded via mw.loadData, which has its metatable stripped by default. This is because it has a protected metatable, and the substitute metatable causes behaviour that is generally unwanted. This exception can be overridden by setting `rawCopy` to true. | |||
-- If `noMetatable` is true, then metatables will not be present in the copy at all. | |||
-- If `keepLoadedData` is true, then any data loaded via mw.loadData will not be copied, and the original will be used instead. This is useful in iterative contexts where it is necessary to copy data being destructively modified, because objects loaded via mw.loadData are immutable. | |||
------------------------------------------------------------------------------------ | |||
function export.deepcopy(orig, noMetatable, rawCopy, keepLoadedData) | |||
local already_seen = {} | |||
local | local function dc(orig, includeMetatable) | ||
if keepLoadedData then | |||
local mt = getmetatable(orig) | |||
if mt and mt.mw_loadData then | |||
return orig | |||
end | |||
end | end | ||
already_seen[orig] = copy | if type(orig) == "table" then | ||
if not already_seen[orig] then | |||
local copy = {} | |||
already_seen[orig] = copy | |||
for key, value in pairs(orig) do | |||
copy[dc(key, includeMetatable)] = dc(value, includeMetatable) | |||
end | |||
if includeMetatable then | |||
local mt = getmetatable(orig) | |||
if type(mt) == "table" and ( | |||
(not mt.mw_loadData) or | |||
(mt.mw_loadData and rawCopy) | |||
) then | |||
setmetatable(copy, dc(mt, includeMetatable)) | |||
end | |||
end | |||
end | end | ||
return already_seen[orig] | |||
else | |||
return orig | |||
end | end | ||
end | end | ||
return | return dc(orig, not noMetatable) | ||
end | end | ||
Line 150: | Line 153: | ||
-- append | -- append | ||
-- | -- | ||
-- This appends | -- This appends any number of tables together and returns the result. Compare the Lisp | ||
-- expression (append list1 list2). | -- expression (append list1 list2 ...). | ||
------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ||
--]] | --]] | ||
function export.append( | function export.append(...) | ||
local ret = {} | local ret = {} | ||
for | for i=1,select('#', ...) do | ||
local argt = select(i, ...) | |||
checkType('append', i, argt, 'table') | |||
for _, v in ipairs(argt) do | |||
table.insert(ret, v) | |||
end | |||
end | end | ||
return ret | return ret | ||
Line 401: | Line 403: | ||
be more efficient. | be more efficient. | ||
If `includeMetatables` is true, then metatables will also be compared. However, | |||
by default, metatables from mw.loadData will not be included in this comparison. | |||
This is because the metatable changes each time mw.loadData is used, even if | |||
it is used on the same data. This can be overridden by setting `rawCompare` to | |||
true. | |||
]] | ]] | ||
function export.deepEquals(x, y) | |||
if type(x) == "table" and type(y) == "table" then | function export.deepEquals(x, y, includeMetatables, rawCompare) | ||
local already_seen = {} | |||
-- This strips metatables only from data loaded via mw.loadData. | |||
if includeMetatables and not rawCompare then | |||
x = export.deepcopy(x) | |||
y = export.deepcopy(y) | |||
end | |||
local function de(x, y) | |||
if type(x) == "table" and type(y) == "table" then | |||
-- Two tables are the same if they have the same number of | |||
-- elements and all keys that are present in one of the tables | |||
-- compare equal to the corresponding keys in the other table, | |||
-- using structural comparison. | |||
-- If an element of x is a table, then its table in `already_seen` | |||
-- is checked for y (which means they have been compared before). | |||
-- If so, immediately iterate to avoid duplicated work. This avoids | |||
-- infinite loops. | |||
if not already_seen[x] then | |||
already_seen[x] = {} | |||
if not already_seen[x][y] then | |||
already_seen[x][y] = true | |||
local sizex = 0 | |||
for key, value in pairs(x) do | |||
if not de(value, y[key]) then | |||
return false | |||
end | |||
sizex = sizex + 1 | |||
end | |||
if includeMetatables and not de(getmetatable(x), getmetatable(y)) then | |||
return false | |||
end | |||
local sizey = export.size(y) | |||
if sizex ~= sizey then | |||
return false | |||
end | |||
end | |||
end | end | ||
return true | |||
end | end | ||
return x == y | |||
end | end | ||
return x | |||
return de(x, y) | |||
end | end | ||
Line 434: | Line 465: | ||
handle such tables correctly but be less efficient on lists than | handle such tables correctly but be less efficient on lists than | ||
export.deepEqualsList. | export.deepEqualsList. | ||
]] | ]] | ||
function export.deepEqualsList(x, y) | function export.deepEqualsList(x, y) | ||
if type(x) == "table" and type(y) == "table" then | local already_seen = {} | ||
local function de(x, y) | |||
if type(x) == "table" and type(y) == "table" then | |||
if not already_seen[x] then | |||
already_seen[x] = {} | |||
if not already_seen[x][y] then | |||
already_seen[x][y] = true | |||
if #x ~= #y then | |||
return false | |||
end | |||
for key, value in pairs(x) do | |||
if not de(value, y[key]) then | |||
return false | |||
end | |||
end | |||
end | |||
end | end | ||
return true | |||
end | end | ||
return | return x == y | ||
end | end | ||
return x | |||
return de(x, y) | |||
end | end | ||
--[[ | --[[ | ||
Given a list and a value to be found, return true if the value is in the array | Given a list and a value to be found, return true if the value is in the array | ||
portion of the list. | portion of the list. Comparison is by value, using `deepEquals`. | ||
NOTE: This used to do shallow comparison by default and accepted a third | |||
'deepCompare' param to do deep comparison. This param is still accepted but now | |||
ignored. | |||
]] | ]] | ||
function export.contains(list, x | function export.contains(list, x) | ||
checkType('contains', 1, list, 'table') | checkType('contains', 1, list, 'table') | ||
for _, v in ipairs(list) do | |||
if export.deepEquals(v, x) then return true end | |||
end | end | ||
return false | return false | ||
Line 474: | Line 511: | ||
--[[ | --[[ | ||
Given a general table and a value to be found, return true if the value is in | Given a general table and a value to be found, return true if the value is in | ||
either the array or hashmap portion of the table. | either the array or hashmap portion of the table. Comparison is by value, using | ||
`deepEquals`. | |||
NOTE: This used to do shallow comparison by default and accepted a third | |||
'deepCompare' param to do deep comparison. This param is still accepted but now | |||
ignored. | |||
]] | ]] | ||
function export.tableContains(tbl, x | function export.tableContains(tbl, x) | ||
checkType('tableContains', 1, tbl, 'table') | checkType('tableContains', 1, tbl, 'table') | ||
for _, v in pairs(tbl) do | |||
if export.deepEquals(v, x) then return true end | |||
end | end | ||
return false | return false | ||
Line 494: | Line 528: | ||
--[[ | --[[ | ||
Given a list and a value to be inserted, append or insert the value if not | Given a list and a value to be inserted, append or insert the value if not | ||
already present in the list. | already present in the list. Comparison is by value, using `deepEquals`. | ||
Appends to the end, like the default behavior of table.insert(), unless `pos` | |||
the end, like the default behavior of table.insert(), unless `pos` is given, | is given, in which case insertion happens at position `pos` (i.e. before the | ||
in which case insertion happens at position `pos` (i.e. before the existing | existing item at position `pos`). | ||
item at position `pos`). | |||
NOTE: The order of `item` and `pos` is reversed in comparison to table.insert(), | NOTE: The order of `item` and `pos` is reversed in comparison to table.insert(), | ||
which uses `table.insert(list, item)` to insert at the end but | which uses `table.insert(list, item)` to insert at the end but | ||
`table.insert(list, pos, item)` to insert at position POS. | `table.insert(list, pos, item)` to insert at position POS. | ||
NOTE: This used to do shallow comparison by default and accepted a fourth | |||
'deepCompare' param to do deep comparison. This param is still accepted but now | |||
ignored. | |||
]] | ]] | ||
function export.insertIfNot(list, item, pos) | |||
function export.insertIfNot(list, item, pos | if not export.contains(list, item) then | ||
if not export.contains(list, item | |||
if pos then | if pos then | ||
table.insert(list, pos, item) | table.insert(list, pos, item) | ||
Line 620: | Line 656: | ||
end | end | ||
end | end | ||
end | |||
--[[ | |||
A set of functions that, given an array and function, iterate through the array applying that function. | |||
`reduce` applies func(r, k, v), and returns the result, where r is the value calculated so far, k is an index, and v is the value at index k. For example, reduce(array, function(a, b) return a + b end) will return the sum of `array`. | |||
`apply` applies func(k, v), and returns the modified array. For example, apply(array, function(a) return 2*a end) will return an array where each member of `array` has been doubled. | |||
`all` returns whether func(k, v) is true for all iterations. | |||
`any` returns whether func(k, v) is true for at least one iteration. | |||
Optional arguments: | |||
i: start index; negative values count from the end of the array | |||
j: end index; negative values count from the end of the array | |||
s: step increment | |||
These must be non-zero integers. | |||
The function will determine where to iterate from, whether to iterate forwards or backwards and by how much, based on these inputs (see examples below for default behaviours). | |||
Examples: | |||
No values for i, j or s results in forward iteration from the start to the end in steps of 1 (the default). | |||
s=-1 results in backward iteration from the end to the start in steps of 1. | |||
i=7, j=3 results in backward iteration from indices 7 to 3 in steps of 1 (i.e. s=-1). | |||
j=-3 results in forward iteration from the start to the 3rd last index. | |||
j=-3, s=-1 results in backward iteration from the end to the 3rd last index. | |||
Note: directionality generally only matters for `reduce`, but values of s > 1 (or s < -1) still affect the return value of `apply`. | |||
]] | |||
local function getIteratorValues(i, j , s, list) | |||
i = (i and i < 0 and #list - i + 1) or i or (s and s < 0 and #list) or 1 | |||
j = (j and j < 0 and #list - j + 1) or j or (s and s < 0 and 1) or #list | |||
s = s or (j < i and -1) or 1 | |||
if ( | |||
i == 0 or i % 1 ~= 0 or | |||
j == 0 or j % 1 ~= 0 or | |||
s == 0 or s % 1 ~= 0 | |||
) then | |||
error("Arguments i, j and s must be non-zero integers.") | |||
end | |||
return i, j, s | |||
end | |||
function export.reduce(list, func, i, j, s) | |||
i, j, s = getIteratorValues(i, j , s, list) | |||
local ret = list[i] | |||
for k = i + s, j, s do | |||
ret = func(ret, k, list[k]) | |||
end | |||
return ret | |||
end | |||
function export.apply(list, func, i, j, s) | |||
local modified_list = export.deepcopy(list) | |||
i, j, s = getIteratorValues(i, j , s, modified_list) | |||
for k = i, j, s do | |||
modified_list[k] = func(k, modified_list[k]) | |||
end | |||
return modified_list | |||
end | |||
function export.all(list, func, i, j, s) | |||
i, j, s = getIteratorValues(i, j , s, list) | |||
local ret = true | |||
for k = i, j, s do | |||
ret = ret and not not (func(k, list[k])) | |||
if not ret then break end | |||
end | |||
return ret | |||
end | |||
function export.any(list, func, i, j, s) | |||
i, j, s = getIteratorValues(i, j , s, list) | |||
local ret = false | |||
for k = i, j, s do | |||
ret = ret or not not (func(k, list[k])) | |||
if ret then break end | |||
end | |||
return ret | |||
end | end | ||
Line 631: | Line 741: | ||
Conjunction to use; defaults to "and". | Conjunction to use; defaults to "and". | ||
- italicizeConj | - italicizeConj | ||
Italicize conjunction: for [[Module | Italicize conjunction: for [[Module:also]] | ||
- dontTag | - dontTag | ||
Don't tag the serial comma and serial "and". For error messages, in | Don't tag the serial comma and serial "and". For error messages, in | ||
Line 745: | Line 855: | ||
end | end | ||
return true | return true | ||
end | |||
--[[ | |||
Add a list of aliases for a given key to a table. The aliases must be given as a table. | |||
--]] | |||
function export.alias(t, k, aliases) | |||
for _, alias in pairs(aliases) do | |||
t[alias] = t[k] | |||
end | |||
end | end | ||
return export | return export |