Module:Table

- --							Module:Table -- -- This module includes a number of functions for dealing with Lua tables. -- It is a meta-module, meant to be called from other Lua modules, and should -- not be called directly from. -- [ CONTENTS ]- -- The following list is all the functions this module houses. -- --  *merge(t1: table, ...items: any) --  *length(t: table) --  *setPrototype(t: table, proto: table, indexFunc?: function, parentProto?: table) --  *isSequence(t: table) --  *push(t: table, ...items: any) --  *isClass(t: table) --  *makeClass(constructor?: function, methods: table, parentClass?: table, options?: table) --  *unshift(t: table, ...items: any) --  *map(t: table, callbackfn: function(v: any, i?: number, t?: table)) --  *filter(t: table, callbackfn: function(v: any, i?: number, t?: table)) --  *removeLast(t: table) --  *removeFirst(t: table) --  *fill(t: table, v: any, startIndex: number, endIndex?: number) --  *indexOf(t: table, v: any) --  *lastIndexOf(t: table, v: any) --  *includes(t: table, v: any) --  *every(t: table, callbackfn: function(k: any, v?: any, t?: table, i?: number)) --  *some(t: table, callbackfn: function(v: any, i: number, t: table)) --  *reduce(t: table, callbackfn: function(accum: any, curVal: any, i?: number, t?: table), initVal: any) --  *reduceRight(t: table, callbackfn: function(acc: any, curVal: any, i?: number, t?: table), initVal: any) --  *keySubset(t: tale) --  *reverseIpairs(t: table) --  *reverse(t: table) --  *empty(t: table) --  *clean(t: table) --  *affixNums(t: table, prefix?: string, suffix?: string) --  *numData(t: table, compress?: boolean) --  *compressSparseArray(t: table) --  *sparseIpairs(t: table) --  *keysToKist(t: table, keySort?: function|boolean, checked?: boolean) --  *invert(t: table) --  *from(t: table) --  *sequenceToTable(t: table) --  *sortedPairsReverse(t: table, keySort?: boolean)  --  *sortedPairs(t: table, keySort?: boolean) --  *recursiveConcat(t: table, sep?: string, i?: number, j?: number) --  *each(t: table, callbackfn: function(k: any, v?: any, t?: table, i?: number) --   *format(t: table) --   *deepCopy(orig: any, noMetatable?: boolean, already_seen?: table) --   *dumpObject(t: table) --   *logObject(t: table) --   *tableUtil(t: table, mt?: table)  -- --[ ATTRIBUTION ] -- Some documentation text of this module was taken form the MDN JS guide -- (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference). -- -- Some Functions were taked from `Module:TableTools` at `en.wikipedia.org`. - local yesno = require('Module:Yesno') local lu = require('Module:LibraryUtil') local string = require('Module:String') local checkType, checkType, customArgError = lu.checkType, lu.checkType, lu.customArgError

--Begin Exports local p = {}

-- Set 'type' function to custom one to force use in other modules local _type = type type = nil local function __type(v) local tp = _type(v) local __t = (getmetatable(v) or {}).__type if __t ~= nil then if _type(__t) == "function" then return __t(v, tp) else return __t end else return tp	end end _G.type = __type

-- Load table library if this module is loaded as "table" p.concat = table.concat p.maxn = table.maxn p.remove = table.remove p.insert = table.insert p.sort = table.sort p.unpack = function(t) local mt = getmetatable(t) local __m = (mt or {}).__unpack if __m then if type(__m) == "function" then return __m(t) else return __m end else return unpack(t) end end p.pack = function(...) return { ... } end

- -- Local Helper functions - local function isPositiveInteger(v) return type(v) == 'number' and v >= 1 and math.floor(v) == v and v < math.huge end

local function isNaN(v) return type(v) == 'number' and tostring(v) == '-nan' end

- -- function: merge(t1: table [the target table], ...items: any) -- -- This function takes each entry in `...items` and adds it to the table. -- If the item is not a table, it adds it to the table like `push`. -- Else, it destructures the table and adds each of its items to the end of `t1`. - function p.merge(t1, ...) local items = p.pack(...) checkType('merge', 1, t1, 'table') customArgError(items[1] == nil, 'merge', 2, 'value expected')

for i, v in ipairs(items) do		if type(v) == "table" then if p.isSequence(v) then for i, value in ipairs(v) do					p.push(t1, value) end else for key, value in pairs(v) do					t1[key] = value end end else p.push(t1, v)		end end return t1 end

- -- function: length(t: table, countNamed?: boolean) -- -- Gets the length of `t`, as the `#` (length operator) does not work on tables -- with named indexes (named indexes can be skipped if the second parameter is omitted). -- function p.length(t, countNamed) checkType(1, t, 'table') checkType(2, countNamed, 'boolean', true) local i = 0 for k in pairs(t) do		if countedNamed then if type(k) == "number" then i = i + 1 end else i = i + 1 end end return i end

- -- function: setPrototype(t: table, proto: table, indexFunc: function) -- -- Sets up a metatable prototype with a table. - function p.setPrototype(t, proto, indexFunc, parentProto) checkType('setPrototype', 1, t, 'table') checkType('setPrototype', 2, proto, 'table') checkType('setPrototype', 3, indexFunc, 'function', true) checkType('setPrototype', 4, parentProto, 'table', true) local mt = {} mt.__proto = proto or {} parentProto = parentProto or {} if p.length(parentProto) ~= 0 then mt.__proto.prototype = parentProto end indexFunc = indexFunc or function(t, k, proto, parentProto) if k == 'prototype' then return mt.__proto else return mt.__proto[k] or (p.length(parentProto or {}) ~= 0 and mt.__proto.prototype[k] or nil) end end mt.__index = function(t, k)		return indexFunc(t, k, mt.__proto, mt.__proto.prototype) end return setmetatable(t, mt) end

- -- function: isSequence(t: table) -- -- Checks if the table is a sequence. - function p.isSequence(t) checkType('isSequence', 1, t, 'table') return p.every(t, function(i) return type(i) == "number" and i > 0 end) end

- -- function: push(t: table, ...items: any) -- -- Appends each of `...items` to the end of `t`. - function p.push(t, ...) local items = p.pack(...) checkType('push', 1, t, 'table') customArgError(items[1] == nil, 'push', 2, 'value expected') customArgError(not p.isSequence(t), 'push', 1, 'provided table is not a sequence') for _, v in pairs(items) do		table.insert(t, v)	end return t end

- -- function: isClass(t: table) -- -- Determines if the table is a class. - function p.isClass(t) if type(t) ~= "table" then return false else return t.isClass or false end end

- -- function: makeClass(constructor?: function, methods: table, parentClass?: table, options?: table) -- -- Creates a class table. - function p.makeClass(constructor, methods, parentClass, options) checkType('makeClass', 1, constructor, { 'function', 'table', 'nil' }) checkType('makeClass', 2, methods, { 'table', 'nil' }) checkType('makeClass', 3, parentClass, { 'table', 'nil' }) checkType('makeClass', 4, options, 'table', true) local options = options or {} customArgError(parentClass ~= nil and not parentClass.isClass, 'makeClass', 3, 'table is not a class')

local ignoreTableRet, methods = unpack{ options.ignoreTableRet or options.ignoreTable or options.ignore, }, 		methods ~= nil and methods or {} local origConstructor = constructor function tmpFunc(t1) return t1	end if type(constructor) == "table" then methods = constructor end if methods.constructor then constructor = methods.constructor elseif not methods.constructor and type(origConstructor) == "table" then constructor = tmpFunc end local util = {} local temp = {} local t2 = {} local reservedNames = p.sequenceToSet({		'constructor',		'parent',		'prototype',		'constructorCalled',		'isClass',	})

local allowedTypes = p.sequenceToSet({		'static',		'get',		'set',		'private',		'readonly',		'protected',		'public',	}) if constructor == nil then constructor = tmpFunc end function nameExists(t, k)		return rawget(t, k) and reservedNames[k] end function util:methodType(f, searchType) checkType(2, searchType, 'string') local f = f or {} local ret if type(f) == "function" then return 'normal' elseif type(f) == "table" and type(f[2]) == "function" then if type(f[1]) == "table" then if not p.every(f[1], function(k, v)					return allowedTypes[v]				end) then ret = false else ret = f[1] end else ret = f[1] end else ret = false end if type(searchType) == "string" then if type(f) == "table" and type(f[1]) == "table" then if p.indexOf(f[1], searchType:lower) ~= -1 then ret = searchType end elseif type(f) == "table" and type(f[1]) == "string" then if searchType:lower == f[1]:lower then return searchType:lower end end elseif type(searchType) == "table" then local tmp = {} for i, v in ipairs(searchType) do				if type(f[1]) == "string" then ret = v:lower == f[1]:lower elseif type(f[1]) == "table" then if p.indexOf(f[1], v:lower) ~= -1 then p.push(tmp, v:lower) end end end ret = tmp end return ret end function util:filterMethods(t, _type, invert) checkType(1, t, 'table') checkType(2, _type, 'string') local ret = {} for k, v in p.sortedPairs(t) do			local tmp = util:methodType(v, _type:lower) == _type:lower local compare if invert then compare = not tmp else compare = tmp end if compare then ret[k] = v			end end return ret end function util:execMethod(f, ...) checkType(1, f, { 'function', 'table' })

local function isValid(v) if type(v) == "function" then return "function" elseif type(v) == "table" and type(v[1]) == "string" and allowedTypes[v[1]] and type(v[2]) == "function" then return "single type" elseif type(v) == "table" and type(v[1]) == "table" and type(v[2]) == "function" and p.every(v[1], function(k, v)				return allowedTypes[v]			end) then return "multi type" else return false end end if isValid(f) == "function" then return f(...) elseif isValid(f) == "single type" or isValid(f) == "multi type" then return f[2](...) else error('Method is not executable', 2) end end function util:new(...) local tmp = {} local t1 = { parent = parentClass or {}, isClass = true, prototype = util:filterMethods(methods, 'static', true), static = t2, }		t1.super = function(...) if p.length(t1.parent) < 1 then error('the class must have a parent when calling the super constructor', 4) end t1.parent = t1.parent:constructor(...) return t1.parent end t1.constructor = util.new local mt = { __index = function(_, k)				local value

if t1.parent ~= false then value = t1.prototype[k] or t1.parent[k] or (t1.parent.prototype or {})[k] else value = t1.prototype[k] end local function mType(type) return util:methodType(value, type) end if mType('get') == 'get' then return util:execMethod(value, t1) elseif (mType('set') == 'set' or mType('static') == 'static') then return nil else return value end end, __newindex = function(_, k, v)				local isSet = util:methodType(rawget(t1.prototype, k), 'set') if isSet then return util:execMethod(rawget(t1.prototype, k), t1, v)				end local function mType(type) return util:methodType(v, type) end if type(v) == "function" then rawset(t1.prototype, k, v)				elseif mType('get') == 'get' or mType('set') == 'set' then rawset(t1.prototype, k, v)				elseif mType('static') == 'static' then rawset(t1.static, k, v)				else rawset(t1, k, v)				end return t1[k] end, }		mt.__metatable = mt		local function methodExists(index) local ind = t1.prototype[index] or (t1.parent and t1.parent.prototype or {})[index] if ind then return true end end local metatableMethods = { __tostring = function return t1:__tostring(t1) end, __concat = function(a, b) return t1:__concat(a, b) end, __call = t1.__call, __pairs = function return t1:__pairs(t1, t1.prototype, t1.static) end, __ipairs = function return t1:__ipairs(t1, t1.prototype, t1.static) end, __add = function(a, b) return t1:__add(a, b) end, __sub = function(a, b) return t1:__sub(a, b) end, __mul = function(a, b) return t1:__mul(a, b) end, __div = function(a, b) return t1:__div(a, b) end, __mod = function(a, b) return t1:__mod(a, b) end, __pow = function(a, b) return t1:__pow(a, b) end, __unm = function(a, b) return t1:__unm(a, b) end, --Custom metamethods __tonumber = function(a) return t1:__tonumber(a) end, __type = function(a, b) return t1:__type(a, b) end, __unpack = function(a, b) return t1:__type(a, b) end, __toboolean = function(a) return t1:__toboolean(a, b) end, __totable = function(a, b, c, d) return t1:__totable(a, b, c, d) end, __tconcat = function(a, b, c, d) return t1:__tconcat(a, b, c, d) end, }

for k, v in pairs(metatableMethods) do			if methodExists(k) then mt[k] = v			end end setmetatable(t1, mt) local constructorValue = constructor(t1, ...) if t1.parent then customArgError(t1.parent.constructorCalled ~= nil and not t1.parent.constructorCalled, 'makeClass', 3, 'parent class constructor must be called if the child constructor was called') end rawset(t1, 'constructorCalled', true) local ret if ignoreTableRet or type(constuctorValue) == "table" then ret = constructorValue else ret = t1		end if ret == nil then ret = t1		end return ret end

t2.constructorCalled = false t2.parent = (parentClass or {}).static or {} t2.prototype = util:filterMethods(methods, 'static') or {} t2.isClass = true t2.prototype.constructor = util.new

local metatableMethods = {} local mt = {}

mt.__call = util.new mt.__index = function(_, k)		local value if parentClass ~= nil then value = t2.prototype[k] or t2.parent[k] or t2.parent.prototype[k] else value = t2.prototype[k] end local function mType(type) return util:methodType(value, type) end if mType('get') == 'get' then return util:execMethod(value, t2) elseif mType('set') == 'set' then return nil elseif mType('static') == 'static' then return value[2] else return value end end mt.__newindex = function(_, k, v)		local isSet = util:methodType(t2[k], 'set') if isSet then return util:execMethod(t2[k], t2, v)		end local function mType(type) return util:methodType(v, type) end if metatableMethods[k] then rawset(t2.prototype, k, v)			rawset(mt, k, metatableMethods[k]) elseif type(v) == "function" then rawset(t2.prototype, k, v)		elseif mType('get') == 'get' or mType('set') == 'set' then rawset(t2.prototype, k, v)		else rawset(t2, k, v)		end return t2[k] end

local function methodExists(index) local ind = (t2.prototype or {})[index] or (t2.parent.prototype or {})[index] if ind then return true end end

metatableMethods.__tostring = function return t2:__tostring(t2) end metatableMethods.__concat = function(a, b) return t2:__concat(a, b) end metatableMethods.__pairs = function return t2:__pairs(t2, t2.prototype) end metatableMethods.__ipairs = function return t2:__ipairs(t2, t2.prototype) end metatableMethods.__add = function(a, b) return t2:__add(a, b) end metatableMethods.__sub = function(a, b) return t2:__sub(a, b) end metatableMethods.__mul = function(a, b) return t2:__mul(a, b) end metatableMethods.__div = function(a, b) return t2:__div(a, b) end metatableMethods.__mod = function(a, b) return t2:__mod(a, b) end metatableMethods.__pow = function(a, b) return t2:__pow(a, b) end metatableMethods.__unm = function(a, b) return t2:__unm(a, b) end --Custom metamethods metatableMethods.__tonumber = function(a) return t2:__tonumber(a) end metatableMethods.__type = function(a, b) return t2:__type(a, b) end metatableMethods.__unpack = function(a, b) return t2:__type(a, b) end metatableMethods.__toboolean = function(a) return t2:__toboolean(a, b) end metatableMethods.__totable = function(a, b, c, d) return t2:__totable(a, b, c, d) end metatableMethods.__tconcat = function(a, b, c, d) return t2:__tconcat(a, b, c, d) end for k, v in pairs(metatableMethods) do		if methodExists(k) then mt[k] = v		end end mt.__metatable = mt	return setmetatable(t2, mt) end

function p.test(...) local Class = p.makeClass{ constructor = function(self, ...) end, test = function(self) return self.static end, staticTest = { 'static', function(self) checkType end }, }

local Child = p.makeClass({		constructor = function(self, ...)			self.super(...)		end,	}, nil, Class) function Class:__concat(...) mw.log(self, ...) end return Class end

- -- function: unshift(t: table, ...items: any) -- -- Prepends each of `...items` to the front of `t`. - function p.unshift(t, ...) local items = p.pack(...) checkType('unshift', 1, t, 'table') customArgError(items[1] == nil, 'unshift', 2, 'value expected') customArgError(not p.isSequence(t), 'unshift', 1, 'provided table is not a sequence') for _, v in ipairs(items) do		table.insert(t, 1, v)	end return t end

- -- function: map(t: table, callbackfn: function(v: any, i?: number, t?: table)) -- -- Creates a new table and populates with results from the callback function. -- ---[ CALLBACK PARAMETERS ]- -- The callback to `map` has three values passed to it described above. -- `v`, the value of the table index, `i`, the table index, and `t`, the actual table. -- `map` will popluate the new table with the return values of this function. - function p.map(t, callbackfn) checkType('map', 1, t, 'table') customArgError(not p.isSequence(t), 'map', 1, 'provided table is not a sequence') checkType('map', 2, callbackfn, 'function') local ret = {} for i, v in ipairs(t) do		ret[#ret+1] = callbackfn(v, i, t) 	end return ret end

- -- function: filter(t: table, callbackfn: function(v: any, i?: number, t?: table)) -- -- Creates a new table with all elements that pass the test implemented by the provided function. -- ---[ CALLBACK PARAMETERS ]- -- callbackfn(v: any, i?: number, t?: table) -- -- The callback to `filter` has three values passed to it described above. -- `v`, the value of the table index, `i`, the table index, and `t`, the actual table. -- `filter` will popluate the new table with the value of the old table if the returned value -- from the callback is true. - function p.filter(t, callbackfn) checkType('filter', 1, t, 'table') customArgError(not p.isSequence(t), 'filter', 1, 'provided table is not a sequence') checkType('filter', 2, callbackfn, 'function') local ret = {} for i, v in ipairs(t) do		if callbackfn(v, i, t) then p.push(ret, v)		end end return ret end

- -- function: removeLast(t: table) -- -- Removes the last element of the table and returns it. - function p.removeLast(t) checkType('removeLast', 1, t, 'table') customArgError(not p.isSequence(t), 'removeLast', 1, 'provided table is not a sequence')

return table.remove(t, p.length(t)) end

- -- function: removeFirst(t: table) -- -- Removes the First element of the table and returns it. - function p.removeFirst(t) checkType('removeFirst', 1, t, 'table') customArgError(not p.isSequence(t), 'removeFirst', 1, 'provided table is not a sequence')

return table.remove(t, 1) end

- -- function: fill(t?: table, v: any, startIndex: number, endIndex?: number) -- -- Sets the table index starting at `startIndex` with the value of `v` ending at `endIndex`. -- If end is not specified, it defualts to the length of the table. - function p.fill(t, v, startIndex, endIndex) checkType('fill', 1, t, { 'table', 'nil' }) customArgError(not p.isSequence(t), 'fill', 1, 'provided table is not a sequence') checkType('fill', 3, startIndex, 'number') checkType('fill', 4, endIndex, { 'number', 'nil' })

local t = t or {}

for i = startIndex, endIndex or #t, 1 do		t[i] = v	end return t end

- -- function: indexOf(t: table, v: any) -- -- Searches for `v` the first index in `t`. If nothing is found, it returns `-1`. - function p.indexOf(t, value) checkType('indexOf', 1, t, 'table') customArgError(not p.isSequence(t), 'indexOf', 1, 'provided table is not a sequence') local index p.some(t, function(k, v) 		if v == value then			index = k			return true 		else			return false		end	end) return index or -1 end

- -- function: lastIndexOf(t: table, v: any) -- -- Searches for `v` the first index in `t`. If nothing is found, it returns `-1`. - function p.lastIndexOf(t, v)	checkType('lastIndexOf', 1, t, 'table') customArgError(not p.isSequence(t), 'lastIndexOf', 1, 'provided table is not a sequence') for i, value in p.reverseIpairs(t) do		if v == value then return i end end return -1 end

- -- function: includes(t: table, v: any) -- -- Checks if `v` is included in any indexes in `t`. - function p.includes(t, _v) checkType('includes', 1, t, 'table') customArgError(not p.isSequence(t), 'includes', 1, 'provided table is not a sequence') return p.some(t, 		function(i, v)			return t[i] == _v 		end  ) end

- -- function: every(t: table, callbackfn: function(k: any, v?: any, t?: table, i?: number)) -- -- Tests every element from the return value from `callbackfn`. If any elements fail -- the test, it returns false. -- ---[ CALLBACK PARAMETERS ]- -- callbackfn(k: any, v?: any, t?: table, i?: number) -- -- If the callback returns false, `every` will consider the test failed and -- return false. --  *`k` is the table key `every` is currently over. --  *`v` is the value of the table key `every` is currently over. --  *`t` is the table `every` was called on. --  *`i` is the number of iterations `every` has iterated over. - function p.every(t, callbackfn) checkType('every', 1, t, 'table') checkType('every', 2, callbackfn, 'function') local i = 0 for k, v in pairs(t) do		i = i + 1 if not callbackfn(k, v, t, i) then return false end end return true end

- -- function: some(t: table, callbackfn: function(v: any, i: number, t: table)) -- -- Tests whether at least one element in the table passes the test implemented -- by the provided function. It returns a Boolean value. -- function p.some(t, callbackfn) checkType('some', 1, t, 'table') checkType('some', 2, callbackfn, 'function') local i = 0 for k, v in pairs(t) do		i = i + 1 if callbackfn(k, v, t, i) then return true end end return false end

- -- function: reduce( --	 t: table, --	 callbackfn: function(accumlator: any, curVal: any, i?: number, t?: table), --	 initVal: any --) -- -- Executes a reducer callback function on each element of the table, resulting in single output value. -- ---[ CALLBACK PARAMETERS ]- -- callbackfn(accumlator: any, curVal: any, i?: number, t?: table) -- -- The callback to `every` has four values passed to it described above. -- `accumlator` is the accumulated value previously returned in the last invocation -- of the callback value of to accumalate. -- `curVal` is the current element being processed in the table. -- `i` is the current index of the processes element. -- `t` is the table `reduce` was called on. - function p.reduce(t, callbackfn, initVal) checkType('reduce', 1, t, 'table') customArgError(not p.isSequence(t), 'reduce', 1, 'provided table is not a sequence') checkType('reduce', 2, callbackfn, 'function') customArgError(callbackfn(, , , ) == nil, 'reduce', 1, 'no return value for callback') local accumulator for i, v in pairs(t) do		if i == 1 then accumulator = initVal and callbackfn(initVal, v, i, t) or v		elseif i ~= 1 then accumulator = callbackfn(accumulator, v, i, t)		end end return accumulator end

- -- function: reduceRight( --	 t: table, --	 callbackfn: function(accumlator: any, curVal: any, i?: number, t?: table), --	 initVal: any --) -- -- Executes a reducer callback function on each element of the table from left to right, -- resulting in single output value. Very similar to `reduce`. -- ---[ CALLBACK PARAMETERS ]- -- callbackfn(accumlator: any, curVal: any, i?: number, t?: table) -- -- The callback to `every` has four values passed to it described above. -- `accumlator` is the accumulated value previously returned in the last invocation -- of the callback value of to accumalate. -- `curVal` is the current element being processed in the table. -- `i` is the current index of the processes element. -- `t` is the table `reduceRight` was called on. - function p.reduceRight(t, callbackfn, initVal) checkType('reduceRight', 1, t, 'table') customArgError(not p.isSequence(t), 'reduceRight', 1, 'provided table is not a sequence') checkType('reduceRight', 2, callbackfn, 'function') customArgError(callbackfn(, , , ) == nil, 'reduceRight', 1, 'no return value for callback') local accumulator for i, v, start in p.reverseIpairs(t) do		if i == start then accumulator = initVal and callbackfn(initVal, v, i, t) or v		elseif i ~= start then accumulator = callbackfn(accumulator, v, i, t)		end end return accumulator end

- -- function: keySubset(t: tale) -- -- This takes a table and returns an array containing the numbers of any numerical -- keys that have non-nil values, sorted in numerical order. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`. - function p.keySubset(t) checkType('keySubset', 1, t, 'table') local nums = {} for k, v in pairs(t) do		if isPositiveInteger(k) then nums[#nums + 1] = k		end end

table.sort(nums) return nums end

- -- function: reverseIpairs(t: table) -- -- Returns a iterator function to iterate backwards over a sequence table. -- This works like `ipairs` except it works backwards, and it provides an additional -- value in the iteration, `start`. The `start` value is the index the function -- started iterating at. -- -[ EXAMPLE ] -- for i, v, start in table.reverseIpairs(t) do --	 -- code block -- end - function p.reverseIpairs(t) checkType('reverseIpairs', 1, t, 'table') customArgError(not p.isSequence(t), 'reverseIpairs', 1, 'provided table is not a sequence') return function(a, i)		checkType('?', 2, i, 'number') checkType('?', 1, a, 'table') i = i - 1 local v = a[i] if v then return i, v, p.length(t) end end, t, p.length(t)+1 end

- -- function: reverse(t: table) -- -- Reverses the table in place. The first array element becomes the last, and -- the last array element becomes the first. - function p.reverse(t) checkType('reverse', 1, t, 'table') customArgError(not p.isSequence(t), 'reverse', 1, 'provided table is not a sequence') local tmp = {} for _, v in p.reverseIpairs(t) do		p.push(tmp, v)	end p.empty(t) for i, v in ipairs(tmp) do		t[i] = v	end return t end

- -- function: empty(t: table) -- -- Empties the table of all keys. - function p.empty(t) checkType('empty', 1, t, 'table')

for k, v in pairs(t) do		t[k] = nil end return t end

- -- function: clean(t: table) -- -- Removes any indexes from the table which are nil, or are not a postive number. - function p.clean(t) checkType('clean', 1, t, 'table') local ret = {} local nums = p.keySubset(t) for _, num in ipairs(nums) do		ret[#ret+1] = t[num] end return ret end

-- function: affixNums(t: table, prefix?: string, suffix?: string) -- -- This takes a table and returns an array containing the numbers of keys with the -- specified prefix and suffix. For example, for the table -- {a1 = 'foo', a3 = 'bar', a6 = 'baz'} and the prefix "a", affixNums will -- return {1, 3, 6}. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`.

function p.affixNums(t, prefix, suffix) checkType('affixNums', 1, t, 'table') checkType('affixNums', 2, prefix, 'string', true) checkType('affixNums', 3, suffix, 'string', true)

local function cleanPattern(s) -- Cleans a pattern so that the magic characters %.[]*+-?^$ are interpreted literally. return s:gsub('([%(%)%%%.%[%]%*%+%-%?%^%$])', '%%%1') end

prefix = prefix or '' suffix = suffix or '' prefix = cleanPattern(prefix) suffix = cleanPattern(suffix) local pattern = table.concat{ '^', prefix, '([1-9]%d*)', suffix, '$' }

local nums = {} for k, v in pairs(t) do		if type(k) == 'string' then local num = mw.ustring.match(k, pattern) if num then nums[#nums + 1] = tonumber(num) end end end table.sort(nums) return nums end

-- function: numData(t: table, compress?: boolean) -- -- Given a table with keys like ("foo1", "bar1", "foo2", "baz2"), returns a table -- of subtables in the format -- { [1] = {foo = 'text', bar = 'text'}, [2] = {foo = 'text', baz = 'text'} } -- Keys that don't end with an integer are stored in a subtable named "other". -- The compress option compresses the table so that it can be iterated over with -- ipairs. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`.

function p.numData(t, compress) checkType('numData', 1, t, 'table') checkType('numData', 2, compress, 'boolean', true) local ret = {} for k, v in pairs(t) do		local prefix, num = mw.ustring.match(tostring(k), '^([^0-9]*)([1-9][0-9]*)$') if num then num = tonumber(num) local subtable = ret[num] or {} if prefix == '' then -- Positional parameters match the blank string; put them at the start of the subtable instead. prefix = 1 end subtable[prefix] = v			ret[num] = subtable else local subtable = ret.other or {} subtable[k] = v			ret.other = subtable end end if compress then local other = ret.other ret = p.compressSparseArray(ret) ret.other = other end return ret end

- -- function: compressSparseArray(t: table) -- -- This takes an array with one or more nil values, and removes the nil values -- while preserving the order, so that the array can be safely traversed with -- ipairs. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`. - function p.compressSparseArray(t) checkType('compressSparseArray', 1, t, 'table') local ret = {} local nums = p.keySubset(t) for _, num in ipairs(nums) do		ret[#ret + 1] = t[num] end return ret end

- -- function: sparseIpairs(t: table) -- -- This is an iterator for sparse arrays. It can be used like `ipairs`, but can -- handle nil values. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`. - function p.sparseIpairs(t) checkType('sparseIpairs', 1, t, 'table') local nums = p.keySubset(t) local i = 0 local lim = #nums return function i = i + 1 if i <= lim then local key = nums[i] return key, t[key] else return nil, nil end end end

-- function: keysToKist(t: table, keySort?: function|boolean, checked?: boolean) -- -- Returns a list of the keys in a table, sorted using either a default -- comparison function or a custom keySort function. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`.

function p.keysToList(t, keySort, checked) if not checked then checkType('keysToList', 1, t, 'table') checkType('keysToList', 2, keySort, { 'function', 'boolean', 'nil' }) end local list = {} local index = 1 for key, value in pairs(t) do		list[index] = key index = index + 1 end if keySort ~= false then keySort = type(keySort) == 'function' and keySort or defaultKeySort table.sort(list, keySort) end return list end

--- -- function: invert(t: table) -- -- Replaces keys with thier values and vice versa for thier values. - function p.invert(t) checkType("invert", 1, t, "table") customArgError(not p.isSequence(t), 'invert', 1, 'table is not a sequence') local ret = {} for i, v in ipairs(t) do		ret[v] = i	end return ret end

- -- function: from(t: table) -- -- Creates a shallow copy of `t`. This means any subtables and functions will be shared. -- Use `deepCopy` for a deep copy function. - function p.from(t) checkType("from", 1, t, "table")

local ret = {} for k, v in p.sortedPairs(t) do		ret[k] = v	end return ret end

- -- function: sequenceToSet(t: table) -- -- Creates a shallow copy of `t`. This means any subtables and functions will be shared. -- Use `deepCopy` for a deep copy function. - function p.sequenceToSet(t) checkType('sequenceToSet', 1, t, 'table') customArgError(not p.isSequence(t), 'invert', 1, 'table is not a sequence') local ret = {} p.each(t, function(v)		ret[v] = true	end) return ret end - -- function: sortedPairs(t: table, keySort?: boolean) -- -- Iterates through a table, with the keys sorted using the keysToList function. -- If there are only numerical keys, sparseIpairs is probably more efficient. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`. - function p.sortedPairs(t, keySort) checkType('sortedPairs', 1, t, 'table') checkType('sortedPairs', 2, keySort, 'function', true) local list = p.keysToList(t, keySort, true) local i = 0 return function i = i + 1 local key = list[i] if key ~= nil then return key, t[key] else return nil, nil end end end

- -- function: recursiveConcat(t: table, sep?: string, i?: number, j?: number) -- -- Takes all values of this table and any subtables then recursively goes through then -- and adds them to new table, then joins concatnates that table. - function p.recursiveConcat(t, sep, k, l)	checkType('recursiveConcat', 1, t, 'table') customArgError(not p.isSequence(t), 'recursiveConcat', 1, 'provided table is not a sequence') checkType('recursiveConcat', 2, sep, 'string', true) checkType('recursiveConcat', 3, k, 'number', true) checkType('recursiveConcat', 4, l, 'number', true)

local ret = {} local function flattenTable(arr, cb) for i = 1, p.length(arr), 1 do			if p.length(arr) ~= 0 and type(arr[i]) == "table" then flattenTable(arr[i], cb) else cb(arr[i]) end end end flattenTable(t, function(v) p.push(ret, v) end) return table.concat(ret, sep or "", k, l) end

- -- function: each(t: table, callbackfn: function(k: any, v?: any, t?: table, i?: number) -- -- Executes a provided function once for each array element. - function p.each(t, callbackfn)	checkType('each', 1, t, 'table')	checkType('each', 2, callbackfn, "function")	local i = 0	for k, v in p.sortedPairs(t) do		callbackfn(v, k, t, i)		i = i + 1	end	return t end

- -- function: format(t: table) -- -- Takes the table and takes each value as an argument and puts it through `string.format`. - function p.format(t) checkType(1, t, { 'table', 'string' }, true) if not ((t or {})[1] or ''):match('%%%a') then return string.parseDualArg(t) end p.each(t, function(v, k, t)		t[k] = p.includes({'string', 'number', 'boolean'}, type(v)) and tostring(v) or v	end) local success, result = pcall(string.format, unpack(t)) if not success then local match = { result:match('bad argument #(%d+) .-%(.- expected, got (.-)%)') } if not match[1] then match = { result:match('bad argument #(%d+) .-%((.-)%)') } end if match[1] then error(string.format('invalid value (%s) at index %s in table for \'format\'', match[2] ~= 'no value' and match[2] or 'nil', match[1]), 2) else error(result, 2) end end return result end

- -- function: deepCopy(t: table) -- -- Recursively goes through the table and copies it preserving all identities of -- the subtables. -- [ ATTRIBUTION ]-- -- This function was taken from `en.wikipedia.org` from `Module:TableTools`. - local function _deepCopy(orig, includeMetatable, already_seen) -- Stores copies of tables indexed by the original table. already_seen = already_seen or {} local copy = already_seen[orig] if copy ~= nil then return copy end if type(orig) == 'table' then copy = {} for orig_key, orig_value in pairs(orig) do			copy[_deepCopy(orig_key, includeMetatable, already_seen)] = _deepCopy(orig_value, includeMetatable, already_seen) end already_seen[orig] = copy if includeMetatable then local mt = getmetatable(orig) if mt ~= nil then local mt_copy = _deepCopy(mt, includeMetatable, already_seen) setmetatable(copy, mt_copy) already_seen[mt] = mt_copy end end else -- number, string, boolean, etc copy = orig end return copy end

function p.deepCopy(orig, noMetatable, already_seen) checkType("deepCopy", 3, already_seen, "table", true) return _deepCopy(orig, not noMetatable, already_seen) end

- -- function: dumpObject(t: table) -- -- Takes the table and recursively goes through each and every key, creating -- a string repersenting the whole table -- ---[ ATTRIBUTION ]-- -- This function was taken from the media wiki source file (../includes/engines/LuaCommon/lualib/mw.lua) - function p.dumpObject(object) local doneTable = {} local doneObj = {} local ct = {} local function sorter(a, b)		local ta, tb = type(a), type(b) if ta ~= tb then return ta < tb		end if ta == 'string' or ta == 'number' then return a < b		end if ta == 'boolean' then return tostring(a) < tostring(b) end return false -- Incomparable end local function _dumpObject(object, indent, expandTable) local tp = type(object) if tp == 'number' or tp == 'nil' or tp == 'boolean' then return tostring(object) elseif tp == 'string' then return string.format("%q", object) elseif tp == 'table' then if not doneObj[object] then local s = tostring(object) if s == 'table' then ct[tp] = (ct[tp] or 0) + 1 doneObj[object] = 'table#' .. ct[tp] else doneObj[object] = s					doneTable[object] = true end end if doneTable[object] or not expandTable then return doneObj[object] end doneTable[object] = true

local ret = { doneObj[object], ' {\n' } local mt = getmetatable(object) if mt then ret[#ret + 1] = string.rep(" ", indent + 2) ret[#ret + 1] = 'metatable = ' ret[#ret + 1] = _dumpObject(mt, indent + 2, false) ret[#ret + 1] = "\n" end

local doneKeys = {} for key, value in ipairs(object) do				doneKeys[key] = true ret[#ret + 1] = string.rep(" ", indent + 2) ret[#ret + 1] = _dumpObject(value, indent + 2, true) ret[#ret + 1] = ',\n' end local keys = {} for key in pairs(object) do				if not doneKeys[key] then keys[#keys + 1] = key end end table.sort(keys, sorter) for i = 1, #keys do				local key = keys[i] ret[#ret + 1] = string.rep(" ", indent + 2) ret[#ret + 1] = '[' ret[#ret + 1] = _dumpObject(key, indent + 3, false) ret[#ret + 1] = '] = ' ret[#ret + 1] = _dumpObject(object[key], indent + 2, true) ret[#ret + 1] = ",\n" end ret[#ret + 1] = string.rep(" ", indent) ret[#ret + 1] = '}' return table.concat(ret) else if not doneObj[object] then ct[tp] = (ct[tp] or 0) + 1 doneObj[object] = table.concat{ tostring(object), '#', ct[tp] } end return doneObj[object] end end return _dumpObject(object, 0, true) end

- -- function: logObject(t: table) -- -- Calls `dumpObject` on the table then logs it using `mw.log`. - function p.logObject(...) local args = p.pack(...) for i, v in ipairs(args) do		args[i] = p.dumpObject(v) end return mw.log(p.unpack(args)) end

- -- function: keys(t) -- -- Returns a table containing all keys of this table in order. - function p.keys(t) local ret = {} for k, v in p.sortedPairs(t) do		p.push(ret, v)	end return ret end

- -- function: entries(t) -- -- Returns a table with each subtable containing the tables key in the first value, -- and the original table's value corresponding to that key. - function p.entries(t) local ret = {} for k, v in p.sortedPairs(t) do		p.push(ret, { k, v }) end return ret end

- -- function: tableUtil(t?: table) -- -- Takes the table and makes all the methods above availble. It also includes -- an option to set a metatable to this table. - function p.tableUtil(t, metatable) local methods = {} local t = t or {} checkType('tableUtil', 1, t, { 'table', 'nil' }) checkType('tableUtil', 2, metatable, { 'table', 'nil' }) local mt = { __index = function(_, k)			return methods[k] end, }	if metatable then if metatable.__index then setmetatable(methods, { __index = metatable.__index }) metatable.__index = nil end for k, v in pairs(metatable) do			mt[k] = v		end end function methods:each(callbackfn) checkType('each', 1, callbackfn, 'function') p.each(t, callbackfn) return self end -	-- Native Functions -	function methods:rawget(k) customArgError(k == nil, 'rawget', 1, 'value expected') return rawget(t, k)	end function methods:rawset(k, v)		customArgError(k == nil, 'rawset', 1, 'value expected') customArgError(v == nil, 'rawset', 2, 'value expected') return rawset(t, k, v)	end function methods:ipairs return ipairs(t) end function methods:pairs return pairs(t) end function methods:next(k) return next(t, k)	end function methods:tonumber(base) checkType('tonumber', 1, base, 'number') return tonumber(t, base) end function methods:tostring return tostring(t) end function methods:type return type(t) end function methods:print p.logObject(t) return self end -	-- Table library -	function methods:concat(sep, i, j)		checkType('concat', 1, sep, { 'string', 'number', 'nil' }) checkType('concat', 2, i, { 'number', 'nil' }) checkType('concat', 3, j, { 'number', 'nil' }) p.each(self, function(k, i)			if type(k) ~= "number" and type(k) ~= "string" then				error(string.format("invalid value (%s) at index %d in table for 'concat'", type(k), i), 4)			end		end) return table.concat(t, sep, i, j)	end function methods:insert(pos, val) checkType('insert', 1, pos, 'number') customArgError(val == nil, 'insert', 1, 'value expected') table.insert(t, pos, val) return self end

function methods:maxn return table.maxn(t) end function methods:remove(pos) checkType('remove', 1, pos, 'number') table.remove(pos) return self end function methods:sort(sorter) checkType('sort', 1, sorter, { 'function' }, true) table.sort(t, sorter) return self end return setmetatable(t, mt) end

--Finish Module/Exports return p