Module:array: Difference between revisions

From Linguifex
Jump to navigation Jump to search
(Created page with "local Array = {} local array_constructor -- Copy table library so as not to unexpectedly change the behavior of code that -- uses it. local array_methods = mw.clone(table) -...")
 
m (1 revision imported)
 
(One intermediate revision by one other user not shown)
Line 1: Line 1:
local Array = {}
local export = {}
local array_constructor


-- Copy table library so as not to unexpectedly change the behavior of code that
local debug_track_module = "Module:debug/track"
-- uses it.
local function_module = "Module:fun"
local array_methods = mw.clone(table)
local table_module = "Module:table"


-- Create version of table.sort that returns the table.
local get_array_mt -- Defined below.
array_methods.sort = function (t, comp)
local getmetatable = getmetatable
table.sort(t, comp)
local ipairs = ipairs
return t
local pairs = pairs
end
local rawget = rawget
local rawset = rawset
local require = require
local select = select
local setmetatable = setmetatable
local sort = table.sort
local type = type
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local upper = string.upper


-- ipairs and unpack operate on arrays.
local array_mt -- Defined below.
array_methods.ipairs = ipairs
array_methods.unpack = unpack


function array_methods:type()
--[==[
local mt = getmetatable(self)
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.]==]
return type(mt) == "table" and mt.__type or nil
local function append(...)
end
append = require(table_module).append
 
return append(...)
function array_methods:adjustIndex(index)
end
index = math.floor(index)
if index < 0 then
local function debug_track(...)
index = #self + index + 1
debug_track = require(debug_track_module)
return debug_track(...)
end
end
return index
end
local function deep_copy(...)
 
deep_copy = require(table_module).deepCopy
-- string.sub-style slicing.
return deep_copy(...)
function array_methods: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
end
if j == nil or type(j) == "number" then
local function list_to_set(...)
j = self:adjust_index(j or -1)
list_to_set = require(table_module).listToSet
else
return list_to_set(...)
error("Expected number, got " .. type(j))
end
end
local new_arr = array_constructor()
local function shallow_copy(...)
local k = 0
shallow_copy = require(table_module).shallowCopy
for index = i, j do
return shallow_copy(...)
k = k + 1
new_arr[k] = self[index]
end
end
return new_arr
end


-- A function to convert string key-table modules such
--[==[
-- as [[Module:languages/data2]] into arrays.
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.]==]
-- "from" is a bad name.
local m_function
-- field_for_key supplies the field name in which the
local function get_m_function()
-- key will be stored.
m_function, get_m_function = require(function_module), nil
local function to_array(map, field_for_key)
return m_function
m_table = m_table or require "Module:table"
end
local arr = {}
local m_table
local i = 0
local function get_m_table()
for key, val in pairs(map) do
m_table, get_m_table = require(table_module), nil
i = i + 1
return m_table
local new_val = m_table.shallowcopy(val)
if field_for_key then
new_val[field_for_key] = key
end
arr[i] = new_val
end
end
return array_constructor(arr)
-- Functions from [[Module:table]] that operate on arrays or sparse arrays.
end
 
-- Functions from [[Module:table]] that operate on arrays or sparse arrays.
-- List copied from [[Module:table/documentation]].
local operate_on_array = {
-- non-sparse
"removeDuplicates", "length", "contains", "serialCommaJoin",
"reverseIpairs", "reverse", "invert", "listToSet", "isArray",
-- sparse
"numKeys", "maxIndex", "compressSparseArray", "sparseIpairs",
-- tables in general
"shallowcopy", "deepcopy",
}
 
-- Not all of these operate on arrays.
local create_new_array = {
-- Functions from [[Module:table]] that create an array.
-- List copied from [[Module:table/documentation]].
-- List copied from [[Module:table/documentation]].
"removeDuplicates", "numKeys", "affixNums", "compressSparseArray",
local m_table_array_funcs
"keysToList", "reverse",
local function get_m_table_array_funcs()
-- Functions from [[Module:table]] that create an table.
m_table_array_funcs = list_to_set{
"shallowcopy", "deepcopy",
-- non-sparse
-- Functions from [[Module:fun]] that create an array.
"signedIndex", "append", "extend", "extendIfNot", "slice", "removeDuplicates", "length",
"map", "filter",
"size", "contains", "serialCommaJoin", "reverseIpairs", "reverse",
}
"invert", "listToSet", "flatten", "isArray",
 
-- sparse
-- Functions from [[Module:fun]] that take an array in the second argument.
"numKeys", "maxIndex", "compressSparseArray", "indexPairs", "indexIpairs",
-- They just have to have the argument order reversed to work as methods of the
"sparseIpairs",
-- array object.
-- tables in general
local second_argument_is_array = { "map", "some", "all", "filter" }
"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
-- Add aliases for the functions from [[Module:table]] whose names
-- contain "array" or "list", which is redundant, and whose names don't conform
-- contain "array" or "list", which is redundant.
-- to the usual camel case.
-- The key redirects to the value.
-- The key redirects to the value.
local alias_of = {
local alias_of = {
compress = "compressSparseArray", keys = "keysToList", toSet = "listToSet",
compress = "compressSparseArray",
deepCopy = "deepcopy", shallowCopy = "shallowcopy",
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)
local function get_module_function(key, module, module_name)
return module[key]  
return module[key] or
or error("No function named " .. tostring(key) .. " in Module:" .. module_name)
error(("Cannot find %s in [[Module:%s]]"):format(mw.dumpObject(key), module_name))
end
end


local function wrap_in_array_constructor(func)
local function wrap_in_array_constructor(func)
return function (...)
return function (...)
return array_constructor(func(...))
return setmetatable(func(...), array_mt or get_array_mt())
end
end
end
end


local function create_array_generating_func(key, module, module_name)
function get_array_mt()
return wrap_in_array_constructor(get_module_function(key, module, module_name))
-- Copy table library so as not to unexpectedly change the behavior of code that
end
-- uses it.
 
local Array = deep_copy(table)
local function reverse_arguments(func)
Array.ipairs = ipairs
return function (a, b)
Array.pairs = pairs
return func(b, a, true)
Array.unpack = unpack
end
Array.listToText = mw.text.listToText
end
 
-- Create version of table.sort that returns the table.
local function underscore_to_camel_case(str)
function Array:sort(comp)
if type(str) ~= "string" then return str end
sort(self, comp)
str = str:gsub("_(.)", string.upper)
return self
return str
end
 
local m_table, m_fun
local Array = {}
Array.__type = "array"
function Array:__index(key)
if type(key) ~= "string" then
return nil
end
end
-- Convert underscores to camel case: num_keys -> numKeys.
function Array:type()
key = underscore_to_camel_case(key)
local mt = getmetatable(self)
return mt and type(mt) == "table" and rawget(mt, "__type") or nil
local val = array_methods[key]
if val then
return val
end
end
key = alias_of[key] or key
local Array_mt = {}
setmetatable(Array, Array_mt)
local func
function Array_mt:__index(key)
m_table = m_table or require "Module:table"
if type(key) ~= "string" then
if m_table.contains(operate_on_array, key) then
return nil
if m_table.contains(create_new_array, key) then
func = create_array_generating_func(key, m_table, "table")
else
func = m_table[key]
end
end
elseif m_table.contains(second_argument_is_array, key) then
m_fun = m_fun or require "Module:fun"
local raw_func = reverse_arguments(get_module_function(key, m_fun, "fun"))
-- Convert underscores to camel case: num_keys -> numKeys.
if m_table.contains(create_new_array, key) then
-- FIXME: this is pointless overhead: remove once nothing relies on it.
func = wrap_in_array_constructor(raw_func)
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
else
func = raw_func
return nil
end
end
rawset(Array, key, func)
return func
end
end
if func then
array_mt = {
array_methods[key] = func
__index = Array,
return func
__type = "array",
}
function array_mt:__add(v)
return setmetatable(append(self, v), array_mt or get_array_mt())
end
end
get_array_mt = nil
return array_mt
end
end


function Array.__add(a, b)
-- A function to convert string key-table modules such
if type(a) == 'table' and type(b) == 'table' then
-- as [[Module:languages/data/2]] into arrays.
m_table = m_table or require "Module:table"
-- "from" is a bad name.
-- field_for_key supplies the field name in which the
local new_arr = array_constructor(m_table.shallowcopy(a))
-- key will be stored.
function export.from(map, field_for_key)
for _, val in ipairs(b) do
local arr, i = {}, 0
new_arr:insert(val)
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
end
arr[i] = new_val
return new_arr
end
end
return setmetatable(arr, array_mt or get_array_mt())
end
end


function Array:new(...)
local export_mt = {}
 
function export_mt:__call(...)
local arr
local arr
if select("#", ...) == 1 and type((...)) == "table" then
if select("#", ...) == 1 and type((...)) == "table" then
arr = ...
arr = ...
local mt = getmetatable(arr)
local mt = getmetatable(arr)
-- If table has been loaded with mw.loadData, copy it to avoid the
-- If table has been loaded with mw.loadData, copy it to avoid the
-- limitations of it being a virtual table.
-- limitations of it being a virtual table.
if mt and mt.mw_loadData then
if mt and type(mt) == "table" and rawget(mt, "mw_loadData") == true then
m_table = m_table or require "Module:table"
arr = shallow_copy(arr)
arr = m_table.shallowcopy(arr)
end
end
else
else
arr = { ... }
arr = {...}
end
end
return setmetatable(arr, self)
return setmetatable(arr, array_mt or get_array_mt())
end
end


-- Declared as local above.
function export_mt:__index(key)
function array_constructor(...)
-- Convert underscores to camel case: num_keys -> numKeys.
return Array:new(...)
-- FIXME: this is pointless overhead: remove once nothing relies on it.
end
 
local array_generating_funcs = { from = to_array }
local Array_library_mt = {
__call = Array.new, __index = array_generating_funcs
}
setmetatable(Array, Array_library_mt)
 
function Array_library_mt:__index(key)
key = underscore_to_camel_case(key)
key = underscore_to_camel_case(key)
key = alias_of[key] or key
key = alias_of[key] or key
if array_generating_funcs[key] then
local func = rawget(self, key)
return array_generating_funcs[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
end
m_table = m_table or require "Module:table"
func = wrap_in_array_constructor(func)
if m_table.contains(create_new_array, key) then
rawset(export, key, func)
local func = create_array_generating_func(key, m_table, "table")
return func
array_generating_funcs[key] = func
return func
end
end
end


return Array
return setmetatable(export, export_mt)

Latest revision as of 17:47, 4 November 2025



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 unpack = unpack or table.unpack -- Lua 5.2 compatibility
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", "extendIfNot", "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)