Module:table/deepCopy
Jump to navigation
Jump to search
Documentation for this module may be created at Module:table/deepCopy/doc
local debug_track_module = "Module:debug/track"
local dump = mw.dumpObject
local error = error
local getmetatable = getmetatable
local next = next
local pairs = pairs
local rawget = rawget
local type = type
local setmetatable = setmetatable
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local tracked
local function make_copy(orig, seen, mt_flag, keep_loaded_data)
local mt, iter, state, init = getmetatable(orig)
-- If `mt` is nil, just use `next`, as it's faster.
if mt == nil then
iter, state, init = next, orig, nil
-- `mt` could be a non-table if `__metatable` has been used, but discard it in such cases.
elseif type(mt) ~= "table" then
mt, iter, state, init = nil, pairs(orig)
-- Data loaded via `mw.loadData`, which sets the key "mw_loadData" to true in the metatable.
elseif rawget(mt, "mw_loadData") == true then
if keep_loaded_data then
seen[orig] = orig
return orig
-- Track instances of such data being copied, which is very inefficient and usually unnecessary.
elseif not tracked then
debug_track("table/deepCopy/loaded data")
tracked = true
end
-- Discard the metatable and use the `__pairs` metamethod.
mt, iter, state, init = nil, pairs(orig)
-- If `mt_flag` is "none", discard the metatable and use the `__pairs` metamethod.
elseif mt_flag == "none" then
mt, iter, state, init = nil, pairs(orig)
-- Otherwise, keep `mt` and use `next` to copy the raw contents.
else
iter, state, init = next, orig, nil
end
local copy = {}
seen[orig] = copy
for k, v in iter, state, init do
if k and type(k) == "table" then
k = seen[k] or make_copy(k, seen, mt_flag, keep_loaded_data)
end
if v and type(v) == "table" then
v = seen[v] or make_copy(v, seen, mt_flag, keep_loaded_data)
end
copy[k] = v
end
if mt == nil or mt_flag == "none" then
return copy
-- Copy the metatable if `mt_flag` is "copy"; otherwise, it will be "keep", so keep it.
elseif mt_flag == "copy" then
mt = seen[mt] or make_copy(mt, seen, mt_flag, keep_loaded_data)
end
return setmetatable(copy, mt)
end
--[==[
Recursive deep copy function. Preserves copied identities of subtables.
A more powerful version of {mw.clone}, with customizable options.
* `metatableFlag` can be one of three options:
*# "none" (the default): `pairs` will be used to copy the table, meaning that any `__pairs` metamethod will be used to copy the table, if available; the resulting table will not have a metatable.
*# "copy": a raw copy of the table will be made (i.e. any `__pairs` metamethod will be ignored), and the copy will be given a copy of the original table's metatable; this ensures that nothing from the original is retained, but may cause metamethods to behave unexpectedly, depending on their implementation.
*# "keep": a raw copy of the table will be made (i.e. any `__pairs` metamethod will be ignored), and the copy will be given the original table's metatable; this is useful when copying objects that inherit methods from a prototype object (e.g. language objects).
* 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.
* Notes:
*# 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.
*# Data loaded via {mw.loadData} is always treated as though the "none" flag is set, because the way it has been implemented causes errors to be thrown if "copy" or "keep" are used with it.]==]
return function(orig, metatableFlag, keepLoadedData)
if metatableFlag == nil then
metatableFlag = "none"
elseif not (metatableFlag == "keep" or metatableFlag == "copy" or metatableFlag == "none") then
error('metatableFlag must be "none", "copy", "keep" or nil; received ' .. dump(metatableFlag))
end
if orig and type(orig) == "table" then
return make_copy(orig, {}, metatableFlag, keepLoadedData)
end
return orig
end