Jump to content

Module:array

From Wiktionary, the free dictionary

Returns an array constructor that produces a table that has a number of functions available as methods: the table library functions, and various vanilla Lua functions and functions from Module:table and Module:fun that operate on arrays or on tables with integer keys.

local Array = require("Module:array")
local nums = Array() -- or Array:new()
nums:type() --> "array"
for i = 1, 5 do
	nums:insert(i)
end
nums:concat(", ") --> "1, 2, 3, 4, 5"

local squares = nums:map(function (num) return num ^ 2 end) -- Returns new array.
squares:concat(', ') --> "1, 4, 9, 16, 25"

local even_squares = squares:filter(function (square) return square % 2 == 0 end)
even_squares:concat(", ") --> "4, 16"

The functions from Module:table and Module:fun are loaded as needed.

Functions from Module:table:

  • compressSparseArray (alias compress), contains, invert, isArray, length, listToSet (alias toSet), maxIndex, numKeys, removeDuplicates, reverse, reverseIpairs, serialCommaJoin, sparseIpairs

Functions from Module:fun. These have a function as the second argument (first argument of method):

  • all, filter, fold, map, some

These functions are included in a funcs subtable of an array (awkward):

  • keysToList, numKeys

The following functions return an array (with the array metatable):

  • compressSparseArray, keysToList, numKeys, removeDuplicates, reverse

The names with underscores instead of camel case can be used as aliases: for instance, arr:to_set() instead of arr:toSet().

The array constructor behaves differently depending on the arguments supplied to it. Without arguments, it creates an empty table. Given a single table, it adds the metatable to it. If the table has been loaded with mw.loadData, it duplicates the table, removing the metatable that is found in tables loaded with mw.loadData. Otherwise, it creates a new table (array) containing the arguments.

The array constructor does this by adding a metatable. This is similar to how all strings have a metatable that allows the string library functions to be used as methods: for instance, ("abc"):sub(1, 1) for string.sub("abc", 1, 1).


local export = {}

local debug_track_module = "Module:debug/track"
local function_module = "Module:fun"
local table_module = "Module:table"

local get_array_mt -- Defined below.
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

local array_mt -- Defined below.

--[==[
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 append(...)
		append = require(table_module).append
		return append(...)
	end
	
	local function debug_track(...)
		debug_track = require(debug_track_module)
		return debug_track(...)
	end
	
	local function deep_copy(...)
		deep_copy = require(table_module).deepCopy
		return deep_copy(...)
	end
	
	local function list_to_set(...)
		list_to_set = require(table_module).listToSet
		return list_to_set(...)
	end
	
	local function shallow_copy(...)
		shallow_copy = require(table_module).shallowCopy
		return shallow_copy(...)
	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
			"signedIndex", "append", "extend", "slice", "removeDuplicates", "length",
			"size", "contains", "serialCommaJoin", "reverseIpairs", "reverse",
			"invert", "listToSet", "flatten", "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.
			"append", "slice", "removeDuplicates", "numKeys", "compressSparseArray",
			"keysToList", "reverse", "flatten",
			-- 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
	local ret = str:gsub("_(.)", upper)
	if ret ~= str then
		debug_track("array/underscore to camel case")
	end
	return ret
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 setmetatable(func(...), array_mt or get_array_mt())
	end
end

function get_array_mt()
	-- Copy table library so as not to unexpectedly change the behavior of code that
	-- uses it.
	local Array = deep_copy(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
	
	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.
		key = underscore_to_camel_case(key)
		key = alias_of[key] or key
		
		local func = rawget(self, key)
		if func ~= nil then
			return func
		elseif (m_table_array_funcs or get_m_table_array_funcs())[key] then
			func = get_module_function(key, m_table or get_m_table(), "table")
			if (m_table_new_array_funcs or get_m_table_new_array_funcs())[key] then
				func = wrap_in_array_constructor(func)
			end
		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")
			--[==[ Once isArray is no longer used:
			function func(a, b, ...)
				return raw_func(b, a, ...)
			end
			]==]
			if key == "fold" then
				function func(t, f, accum)
					return raw_func(f, t, accum)
				end
			else
				function func(a, b) -- TODO: isArray parameter is probably unnecessary, and doesn't work with sparse arrays anyway.
					debug_track("array/isArray")
					return raw_func(b, a, "isArray")
				end
			end
			if (m_function_new_array_funcs or get_m_function_new_array_funcs())[key] then
				func = wrap_in_array_constructor(func)
			end
		else
			return nil
		end
		
		rawset(Array, key, func)
		return func
	end
	
	array_mt = {
		__index = Array,
		__type = "array",
	}
	
	function array_mt:__add(v)
		return setmetatable(append(self, v), array_mt or get_array_mt())
	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 = shallow_copy(val)
		if field_for_key then
			new_val[field_for_key] = key
		end
		arr[i] = new_val
	end
	return setmetatable(arr, array_mt or get_array_mt())
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 = shallow_copy(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.
	key = underscore_to_camel_case(key)
	key = alias_of[key] or key
	
	local func = rawget(self, key)
	if func ~= nil then
		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
	
	func = wrap_in_array_constructor(func)
	
	rawset(export, key, func)
	return func
end

return setmetatable(export, export_mt)