Module:array
Documentation for this module may be created at Module:array/doc
local export = {}
local debug_track_module = "Module:debug/track"
local function_module = "Module:fun"
local table_module = "Module:table"
local getmetatable = getmetatable
local ipairs = ipairs
local pairs = pairs
local rawget = rawget
local rawset = rawset
local require = require
local select = select
local setmetatable = setmetatable
local sort = table.sort
local type = type
local upper = string.upper
--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function deepcopy(...)
deepcopy = require(table_module).deepcopy
return deepcopy(...)
end
local function list_to_set(...)
list_to_set = require(table_module).listToSet
return list_to_set(...)
end
local function shallowcopy(...)
shallowcopy = require(table_module).shallowcopy
return shallowcopy(...)
end
--[==[
Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==]
local m_function
local function get_m_function()
m_function, get_m_function = require(function_module), nil
return m_function
end
local m_table
local function get_m_table()
m_table, get_m_table = require(table_module), nil
return m_table
end
-- Functions from [[Module:table]] that operate on arrays or sparse arrays.
-- List copied from [[Module:table/documentation]].
local m_table_array_funcs
local function get_m_table_array_funcs()
m_table_array_funcs = list_to_set{
-- non-sparse
"removeDuplicates", "length", "size", "contains", "serialCommaJoin",
"reverseIpairs", "reverse", "invert", "listToSet", "isArray",
-- sparse
"numKeys", "maxIndex", "compressSparseArray", "indexPairs", "indexIpairs",
"sparseIpairs",
-- tables in general
"shallowcopy", "deepcopy",
}
get_m_table_array_funcs = nil
return m_table_array_funcs
end
-- Functions from [[Module:fun]] that take an array in the second argument.
-- They just have to have the argument order reversed to work as methods of the
-- array object.
local m_function_array_funcs
local function get_m_function_array_funcs()
m_function_array_funcs = list_to_set{
"map", "some", "all", "filter", "fold"
}
get_m_function_array_funcs = nil
return m_function_array_funcs
end
-- Functions from [[Module:table]] that create an array or table.
-- Not all of these operate on arrays.
local m_table_new_array_funcs
local function get_m_table_new_array_funcs()
m_table_new_array_funcs = list_to_set{
-- Array.
"removeDuplicates", "numKeys", "compressSparseArray",
"keysToList", "reverse",
-- Array or table.
"shallowcopy", "deepcopy",
}
get_m_table_new_array_funcs = nil
return m_table_new_array_funcs
end
-- Functions from [[Module:fun]] that create an array or table.
-- Not all of these operate on arrays.
local m_function_new_array_funcs
local function get_m_function_new_array_funcs()
m_function_new_array_funcs = list_to_set{
"map", "filter",
}
get_m_function_new_array_funcs = nil
return m_function_new_array_funcs
end
-- Add aliases for the functions from [[Module:table]] whose names
-- contain "array" or "list", which is redundant.
-- The key redirects to the value.
local alias_of = {
compress = "compressSparseArray",
keys = "keysToList",
toSet = "listToSet",
}
local function underscore_to_camel_case(str)
if type(str) ~= "string" then
return str
end
return (str:gsub("_(.)", upper))
end
local function track_underscore_to_camel_case()
debug_track("array/underscore to camel case")
end
local function get_module_function(key, module, module_name)
return module[key] or
error(("Cannot find %s in [[Module:%s]]"):format(mw.dumpObject(key), module_name))
end
local function wrap_in_array_constructor(func)
return function (...)
return export(func(...))
end
end
local function maybe_wrap_in_array_constructor(func, key, new_array_funcs)
if not new_array_funcs[key] then
return func
end
return wrap_in_array_constructor(func)
end
local array_mt
local function get_array_mt()
-- Copy table library so as not to unexpectedly change the behavior of code that
-- uses it.
local Array = deepcopy(table)
Array.ipairs = ipairs
Array.pairs = pairs
Array.unpack = unpack
Array.listToText = mw.text.listToText
-- Create version of table.sort that returns the table.
function Array:sort(comp)
sort(self, comp)
return self
end
function Array:type()
local mt = getmetatable(self)
return mt and type(mt) == "table" and rawget(mt, "__type") or nil
end
function Array:adjustIndex(index)
index = index - index % 1
if index < 0 then
index = self:length() + index + 1
end
return index
end
-- string.sub-style slicing.
function Array:slice(i, j)
if i == nil then
i = 1
elseif type(i) == "number" then
i = self:adjust_index(i)
else
error("Expected number, got " .. type(i))
end
if j == nil or type(j) == "number" then
j = self:adjust_index(j or -1)
else
error("Expected number, got " .. type(j))
end
local new_arr = export()
local k = 0
for index = i, j do
k = k + 1
new_arr[k] = self[index]
end
return new_arr
end
local Array_mt = {}
setmetatable(Array, Array_mt)
function Array_mt:__index(key)
if type(key) ~= "string" then
return nil
end
-- Convert underscores to camel case: num_keys -> numKeys.
-- FIXME: this is pointless overhead: remove once nothing relies on it.
local normalized_key = underscore_to_camel_case(key)
if normalized_key ~= key then
key = normalized_key
normalized_key = true
else
normalized_key = false
end
key = alias_of[key] or key
local func = rawget(self, key)
if func ~= nil then
if normalized_key then
track_underscore_to_camel_case()
end
return func
elseif (m_table_array_funcs or get_m_table_array_funcs())[key] then
func = maybe_wrap_in_array_constructor(
get_module_function(key, m_table or get_m_table(), "table"),
key,
m_table_new_array_funcs or get_m_table_new_array_funcs()
)
elseif (m_function_array_funcs or get_m_function_array_funcs())[key] then
local raw_func = get_module_function(key, m_function or get_m_function(), "fun")
if key == "fold" then
function func(t, f, accum)
return raw_func(f, t, accum)
end
else
function func(a, b)
return raw_func(b, a, true)
end
end
func = maybe_wrap_in_array_constructor(
func,
key,
m_function_new_array_funcs or get_m_function_new_array_funcs()
)
else
return nil
end
if normalized_key then
track_underscore_to_camel_case()
end
rawset(Array, key, func)
return func
end
array_mt = {
__index = Array,
__type = "array",
}
function array_mt.__add(a, b)
if not (type(a) == "table" and type(b) == "table") then
return a - b -- Force arithmetic error.
end
local new_arr = export.shallowcopy(a)
for _, val in ipairs(b) do
new_arr:insert(val)
end
return new_arr
end
get_array_mt = nil
return array_mt
end
-- A function to convert string key-table modules such
-- as [[Module:languages/data/2]] into arrays.
-- "from" is a bad name.
-- field_for_key supplies the field name in which the
-- key will be stored.
function export.from(map, field_for_key)
local arr, i = {}, 0
for key, val in pairs(map) do
i = i + 1
local new_val = shallowcopy(val)
if field_for_key then
new_val[field_for_key] = key
end
arr[i] = new_val
end
return export(arr)
end
local export_mt = {}
function export_mt:__call(...)
local arr
if select("#", ...) == 1 and type((...)) == "table" then
arr = ...
local mt = getmetatable(arr)
-- If table has been loaded with mw.loadData, copy it to avoid the
-- limitations of it being a virtual table.
if mt and type(mt) == "table" and rawget(mt, "mw_loadData") == true then
arr = shallowcopy(arr)
end
else
arr = {...}
end
return setmetatable(arr, array_mt or get_array_mt())
end
function export_mt:__index(key)
-- Convert underscores to camel case: num_keys -> numKeys.
-- FIXME: this is pointless overhead: remove once nothing relies on it.
local normalized_key = underscore_to_camel_case(key)
if normalized_key ~= key then
key = normalized_key
normalized_key = true
else
normalized_key = false
end
key = alias_of[key] or key
local func = rawget(self, key)
if func ~= nil then
if normalized_key then
track_underscore_to_camel_case()
end
return func
elseif (m_table_new_array_funcs or get_m_table_new_array_funcs())[key] then
func = get_module_function(key, m_table or get_m_table(), "table")
elseif (m_function_new_array_funcs or get_m_function_new_array_funcs())[key] then
func = get_module_function(key, m_function or get_m_function(), "fun")
else
return nil
end
if normalized_key then
track_underscore_to_camel_case()
end
func = wrap_in_array_constructor(func)
rawset(export, key, func)
return func
end
return setmetatable(export, export_mt)