Skip to content

Instantly share code, notes, and snippets.

@Deco
Created October 31, 2012 05:38
Show Gist options
  • Save Deco/3985043 to your computer and use it in GitHub Desktop.
Save Deco/3985043 to your computer and use it in GitHub Desktop.
Lua Non-recursive Deep-copy
--[[ deepcopy.lua
Deep-copy function for Lua 5.2 - v0.1
==============================
- Does not overflow the stack.
- Maintains cyclic-references
- Copies metatables
- Maintains common upvalues between copied functions
TODO
----
- Clean up
- Fix for Lua 5.1 and LuaJIT
- Provide option for custom type handling
- Provide option to only set metatables, not copy (as if they were immutable)
Copyright (C) 2012 Declan White
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
]]
function copy(root, dbg, env)
local ORIG, COPY, RETTO, STATE, PART_SIZE = 0, 1, 2, 3, 4
local stack_ptr, stack_top, stack = 1, 4, {
--orig,copy,retto,state,
-- LOOP(key,keycopy,retto,keystate,
-- LOOP(keykey,keykeycopy,retto,keykeystate, ..),
-- value,valuecopy,retto,valuestate,
-- LOOP(..)
-- )
root, nil, -1, nil
}
local copy_dict = stack--[[{
--[orig] = copy,
}]]
local dbg_label_dict, dbg_pause, dbg_its = {"root", "copy", "ret", "state"}, true, 900
dbg = dbg and function(op, ...)
local opidx_dict = {}
if op == 'set' then
local arg_list = {...}
for arg_i = 1, #arg_list, 2 do
local idx, label = arg_list[arg_i+0], arg_list[arg_i+1]
dbg_label_dict[idx] = label
opidx_dict[idx] = true
end
end
local line_stack, line_label, line_stptr = "", "", ""
for stack_i = 1, math.max(stack_top, stack_ptr) do
local s_stack = (
(type(stack[stack_i]) == 'table' or type(stack[stack_i]) == 'function')
and string.gsub(tostring(stack[stack_i]), "^.-(%x%x%x%x%x%x%x%x)$", "<%1>")
or tostring(stack[stack_i])
), type(stack[stack_i])
local s_label = dbg_label_dict[stack_i] or "?!?"
local s_stptr = (stack_i == stack_ptr and "*" or "")..(opidx_dict[stack_i] and "^" or "")
local maxlen = math.max(#s_stack, #s_label, #s_stptr)+1
line_stack = line_stack..s_stack..string.rep(" ", maxlen-#s_stack)
line_label = line_label..s_label..string.rep(" ", maxlen-#s_label)
line_stptr = line_stptr..s_stptr..string.rep(" ", maxlen-#s_stptr)
end
io.stdout:write(
line_stack
.. "\n"..line_label
.. "\n"..line_stptr
.. (dbg_pause and "" or "\n")
)
local maxlen = math.max(#line_stack, #line_label, #line_stptr)
if dbg_pause then io.stdout:write(string.rep(" ",70-maxlen), dbg_its) io.read() end
end or function() end
while stack_ptr > 0 and dbg_its > 0 do
dbg()
--local stack_top_last = stack_top
local this_orig = stack[stack_ptr+ORIG]
local this_copy = stack[stack_ptr+COPY]
local this_state = stack[stack_ptr+STATE]
local this_orig_type = type(this_orig)
local this_done = false
if this_orig_type == 'table' then
local grabkey, key_orig, value_orig = false, nil, nil
if this_state == nil then
-- .., orig, copy, retto, state
this_copy = copy_dict[this_orig]
if this_copy ~= nil then
stack[stack_ptr+COPY] = this_copy dbg('set', stack_ptr+COPY, "copy")
stack[stack_ptr+STATE] = 'done' dbg('set', stack_ptr+STATE, "state")
else
this_copy = {} stack[stack_ptr+COPY] = this_copy dbg('set', stack_ptr+COPY, "copy")
copy_dict[this_orig] = this_copy
local this_orig_meta = debug.getmetatable(this_orig)
if this_orig_meta ~= nil then
stack[stack_ptr+STATE] = 'metatable' dbg('set', stack_ptr+STATE, "state")
local retto = stack_ptr
stack_top = stack_top+PART_SIZE
stack_ptr = stack_top-PART_SIZE+1
stack[stack_ptr+ORIG ] = this_orig_meta
stack[stack_ptr+COPY ] = nil
stack[stack_ptr+RETTO] = retto
stack[stack_ptr+STATE] = nil
dbg('set', stack_ptr+ORIG , "meta"
, stack_ptr+COPY , "copy"
, stack_ptr+RETTO, "ret"
, stack_ptr+STATE, "state"
)
else
grabkey = true
end
end
elseif this_state == 'metatable' then
-- .., orig, copy, retto, state, metaorig, metacopy
--local this_orig_meta = stack[stack_ptr+PART_SIZE+2*0+ORIG]
local this_copy_meta = stack[stack_ptr+PART_SIZE+2*0+COPY]
stack_top = stack_top-2 -- pop metaorig, metacopy
debug.setmetatable(this_copy, this_copy_meta)
grabkey = true
elseif this_state == 'key' then
-- .., orig, copy, retto, state, keyorig, keycopy
key_orig = stack[stack_ptr+PART_SIZE+ORIG]
if key_orig == nil then
-- done
stack_top = stack_top-2 -- pop keyorig, keycopy
stack[stack_ptr+STATE] = 'done' dbg('set', stack_ptr+3, "state")
else
value_orig = this_orig[key_orig]
stack[stack_ptr+STATE] = 'value' dbg('set', stack_ptr+STATE, "state")
local retto = stack_ptr
stack_top = stack_top+PART_SIZE
stack_ptr = stack_top-PART_SIZE+1
stack[stack_ptr+ORIG ] = value_orig
stack[stack_ptr+COPY ] = nil
stack[stack_ptr+RETTO] = retto
stack[stack_ptr+STATE] = nil
dbg('set', stack_ptr+ORIG , "value"
, stack_ptr+COPY , "copy"
, stack_ptr+RETTO, "ret"
, stack_ptr+STATE, "state"
)
end
elseif this_state == 'value' then
-- .., orig, copy, retto, state, keyorig, keycopy, valueorig, valuecopy
key_orig = stack[stack_ptr+PART_SIZE+2*0+ORIG]
--value_orig = stack[stack_ptr+PART_SIZE+2*1+ORIG]
local key_copy = stack[stack_ptr+PART_SIZE+2*0+COPY]
local value_copy = stack[stack_ptr+PART_SIZE+2*1+COPY]
this_copy[key_copy] = value_copy
stack_top = stack_top-4
grabkey = true -- next!
end
if grabkey then
local key_orig, value_orig = next(this_orig, key_orig)
if key_orig ~= nil then
stack[stack_ptr+STATE] = 'key' dbg('set', stack_ptr+STATE, "state")
local retto = stack_ptr
stack_top = stack_top+PART_SIZE
stack_ptr = stack_top-PART_SIZE+1
stack[stack_ptr+ORIG ] = key_orig
stack[stack_ptr+COPY ] = nil
stack[stack_ptr+RETTO] = retto
stack[stack_ptr+STATE] = nil
dbg('set', stack_ptr+ORIG , "key"
, stack_ptr+COPY , "copy"
, stack_ptr+RETTO, "ret"
, stack_ptr+STATE, "state"
)
else
stack[stack_ptr+STATE] = 'done' dbg('set', stack_ptr+STATE, "state")
end
end
elseif this_orig_type == 'function' then
local grabupvalue = false
if this_state == nil then
-- .., orig, copy, retto, state
this_copy = copy_dict[this_orig]
if this_copy ~= nil then
stack[stack_ptr+COPY] = this_copy dbg('set', stack_ptr+COPY, "copy")
stack[stack_ptr+STATE] = 'done' dbg('set', stack_ptr+STATE, "state")
else
this_copy = loadstring(string.dump(this_orig), nil, nil, env)
stack[stack_ptr+COPY] = this_copy dbg('set', stack_ptr+COPY, "copy")
copy_dict[this_orig] = this_copy
stack_top = stack_top+1
stack[stack_ptr+PART_SIZE+0] = 1
grabupvalue = true
end
elseif this_state == 'upvalue' then
-- .., orig, copy, retto, state, uvidx, uvvalueorig, uvvaluecopy
local upvalue_idx = stack[stack_ptr+PART_SIZE+0]
--local upvalue_value_orig = stack[stack_ptr+PART_SIZE+1+ORIG]
local upvalue_value_copy = stack[stack_ptr+PART_SIZE+1+ORIG]
stack_top = stack_top-2 -- pop uvvalueorig, uvvaluecopy
debug.setupvalue(this_copy, upvalue_idx, upvalue_value_copy)
stack[stack_ptr+PART_SIZE] = upvalue_idx+1
grabupvalue = true
end
if grabupvalue then
-- .., orig, copy, retto, state, uvidx
local upvalue_idx_curr = stack[stack_ptr+PART_SIZE+0]
for upvalue_idx = upvalue_idx_curr, math.huge do
local upvalue_name, upvalue_value_orig = debug.getupvalue(this_orig, upvalue_idx)
if upvalue_name ~= nil then
local upvalue_uid = debug.upvalueid(this_orig, upvalue_idx)
-- Attempting to store an upvalueid of a function under root is UB!
local other_orig = copy_dict[upvalue_uid]
if other_orig ~= nil then
for other_upvalue_idx = 1, math.huge do
if upvalue_uid == debug.upvalueid(other_orig, other_upvalue_idx) then
local other_copy = copy_dict[other_orig]
debug.upvaluejoin(
this_copy, upvalue_idx,
other_copy, other_upvalue_idx
)
break
end
end
else
copy_dict[upvalue_uid] = this_orig
if upvalue_value_orig ~= nil then
stack[stack_ptr+STATE] = 'upvalue' dbg('set', stack_ptr+STATE, "state")
local retto = stack_ptr
stack_top = stack_top+PART_SIZE
stack_ptr = stack_top-PART_SIZE+1
stack[stack_ptr+ORIG ] = upvalue_value_orig
stack[stack_ptr+COPY ] = nil
stack[stack_ptr+RETTO] = retto
stack[stack_ptr+STATE] = nil
dbg('set', stack_ptr+ORIG , "uv"
, stack_ptr+COPY , "copy"
, stack_ptr+RETTO, "ret"
, stack_ptr+STATE, "state"
)
break
end
end
else
stack_top = stack_top-1 -- pop uvidx
stack[stack_ptr+STATE] = 'done' dbg('set', stack_ptr+3, "state")
break
end
end
end
elseif (
this_orig_type == 'number'
or this_orig_type == 'string'
or this_orig_type == 'boolean'
or this_orig_type == 'function'
) then
else error('NYI:'..this_orig_type) end
if stack[stack_ptr+STATE] == 'done' then
-- .., orig, copy, retto, state
local retto = stack[stack_ptr+RETTO]
stack_top = stack_top-2 -- pop retto, state
-- .., orig, copy
stack_ptr = retto
--for stack_i = stack_top, stack_top_last do
-- stack[stack_i] = nil
--end
end
dbg_its = dbg_its-1
end
return stack[1+COPY]
end
function main(...)
local v
do
local uv = nil
v = {
set = function(v) uv = v return uv end,
get = function() return uv end,
cactus = {
a = function() uv = 22 end
}
}
end
local nv = copy(v)
print(stringify(nv))
print("v.set("..tostring(v.set(5))..")")
print("nv.set("..tostring(nv.set(6))..")")
print("v.get() == "..tostring(v.get()))
print("nv.get() == "..tostring(nv.get()))
print("v a!") v.cactus.a()
print("v.get() == "..tostring(v.get()))
print("nv.get() == "..tostring(nv.get()))
print("nv a!") nv.cactus.a()
print("v.get() == "..tostring(v.get()))
print("nv.get() == "..tostring(nv.get()))
end
_stringify = function(stack, this, spacing_h, spacing_v, space_n, parsed)
local this_type = type(this)
if this_type == "string" then
stack[#stack+1] = (
spacing_v ~= "\n" and string.gsub(string.format("%q", this), "\\\n", "\\n")
or string.format("%q", this)
)
elseif this_type == "boolean" then
stack[#stack+1] = this and "true" or "false"
elseif this_type == "number" then
stack[#stack+1] = tostring(this)
elseif this_type == "function" then
local info = debug.getinfo(this, "S")
stack[#stack+1] = "function"
stack[#stack+1] = ":("
if not info or info.what == "C" then
stack[#stack+1] = "[C]"
else
--[[local param_list = debug.getparams(this)
for param_i = 1, #param_list do
stack[#stack+1] = param_list[param_i]
end]]
end
stack[#stack+1] = ")"
elseif this_type == "table" then
if parsed[this] then
stack[#stack+1] = "<"..tostring(this)..">"
else
parsed[this] = true
stack[#stack+1] = "{"..spacing_v
for key,val in pairs(this) do
stack[#stack+1] = string.rep(spacing_h, space_n).."["
_stringify(stack, key, spacing_h, spacing_v, space_n+1, parsed)
stack[#stack+1] = "] = "
_stringify(stack, val, spacing_h, spacing_v, space_n+1, parsed)
stack[#stack+1] = ","..spacing_v
end
stack[#stack+1] = string.rep(spacing_h, space_n-1).."}"
end
elseif this_type == "nil" then
stack[#stack+1] = "nil"
else
stack[#stack+1] = this_type.."<"..tostring(this)..">"
end
end
stringify = function(this, docol, spacing_h, spacing_v, preindent)
local stack = {}
_stringify(
stack,
this,
spacing_h or " ", spacing_v or "\n",
(tonumber(preindent) or 0)+1,
{}
)
return table.concat(stack)
end
main(...)
@LoganDark
Copy link

"table.deecopy"

Copy link

ghost commented Oct 27, 2021

♂deep♂ copy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment