Skip to content

Instantly share code, notes, and snippets.

@Deco
Created October 31, 2012 05:38

Revisions

  1. Deco revised this gist Oct 31, 2012. 3 changed files with 25 additions and 112 deletions.
    109 changes: 0 additions & 109 deletions deepcopy.lua
    Original file line number Diff line number Diff line change
    @@ -423,112 +423,3 @@ do
    end
    end
    end


    function main(...)
    -- cyclic table
    local v
    do
    v = {
    'a',
    cactus = {
    6,
    abc = { 22 },
    },
    'c'
    }
    v.cactus.abc.q = v
    end
    --[[setfenv(table.deepcopy,
    setmetatable({}, {
    __index = function(t,k ) error(("_G[%q]!" ):format(tostring(k) ), 2) end,
    __newindex = function(t,k,v) error(("_G[%q] = %q!"):format(tostring(k),tostring(v)), 2) end,
    })
    )]]
    local nv = table.deepcopy(v)
    print(stringify(nv))

    -- common upvalue
    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
    nv = table.deepcopy(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(...)
    27 changes: 25 additions & 2 deletions deepcopy_test.lua
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,29 @@


    function main(...)
    -- cyclic table
    local v
    do
    v = {
    'a',
    cactus = {
    6,
    abc = { 22 },
    },
    'c'
    }
    v.cactus.abc.q = v
    end
    --[[setfenv(table.deepcopy,
    setmetatable({}, {
    __index = function(t,k ) error(("_G[%q]!" ):format(tostring(k) ), 2) end,
    __newindex = function(t,k,v) error(("_G[%q] = %q!"):format(tostring(k),tostring(v)), 2) end,
    })
    )]]
    local nv = table.deepcopy(v)
    print(stringify(nv))

    -- common upvalue
    do
    local uv = nil
    v = {
    @@ -11,7 +34,7 @@ function main(...)
    }
    }
    end
    local nv = copy(v)
    nv = table.deepcopy(v)
    print(stringify(nv))

    print("v.set("..tostring(v.set(5))..")")
    @@ -28,4 +51,4 @@ end

    require"stringify"

    main(...)
    main(...)
    1 change: 0 additions & 1 deletion stringify.lua
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,3 @@

    _stringify = function(stack, this, spacing_h, spacing_v, space_n, parsed)
    local this_type = type(this)
    if this_type == "string" then
  2. Deco revised this gist Oct 31, 2012. 3 changed files with 530 additions and 277 deletions.
    692 changes: 472 additions & 220 deletions deepcopy.lua
    Original file line number Diff line number Diff line change
    @@ -1,19 +1,74 @@
    --[[ deepcopy.lua
    Deep-copy function for Lua 5.2 - v0.1
    Deep-copy function for Lua - v0.2
    ==============================
    - Does not overflow the stack.
    - Maintains cyclic-references
    - Copies metatables
    - Maintains common upvalues between copied functions
    - Maintains common upvalues between copied functions (for Lua 5.2 only)
    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)
    - Document usage (properly) and provide examples
    - Implement handling of LuaJIT FFI ctypes
    - Provide option to only set metatables, not copy (as if they were
    immutable)
    - Find a way to replicate `debug.upvalueid` and `debug.upvaluejoin` in
    Lua 5.1
    - Copy function environments in Lua 5.1 and LuaJIT
    (Lua 5.2's _ENV is actually a good idea!)
    - Handle C functions
    Usage
    -----
    copy = table.deecopy(orig)
    copy = table.deecopy(orig, params, customcopyfunc_list)
    `params` is a table of parameters to inform the copy functions how to
    copy the data. The default ones available are:
    - `value_ignore` (`table`/`nil`): any keys in this table will not be
    copied (value should be `true`). (default: `nil`)
    - `value_translate` (`table`/`nil`): any keys in this table will result
    in the associated value, rather than a copy. (default: `nil`)
    (Note: this can be useful for global tables: {[math] = math, ..})
    - `metatable_immutable` (`boolean`): assume metatables are immutable and
    do not copy them (only set). (default: `false`)
    - `function_immutable` (`boolean`): do not copy function values; instead
    use the original value. (default: `false`)
    - `function_env` (`table`/`nil`): Set the enviroment of functions to
    this value (via fourth arg of `loadstring`). (default: `nil`)
    this value. (default: `nil`)
    - `function_upvalue_isolate` (`boolean`): do not join common upvalues of
    copied functions (only applicable for Lua 5.2 and LuaJIT). (default:
    `false`)
    - `function_upvalue_dontcopy` (`boolean`): do not copy upvalue values
    (does not stop joining). (default: `false`)
    `customcopyfunc_list` is a table of typenames to copy functions.
    For example, a simple solution for userdata:
    { ["userdata"] = function(stack, orig, copy, state, arg1, arg2)
    if state == nil then
    copy = orig
    local orig_uservalue = debug.getuservalue(orig)
    if orig_uservalue ~= nil then
    stack:recurse(orig_uservalue)
    return copy, 'uservalue'
    end
    return copy, true
    elseif state == 'uservalue' then
    local copy_uservalue = arg2
    if copy_uservalue ~= nil then
    debug.setuservalue(copy, copy_uservalue)
    end
    return copy, true
    end
    end }
    Any parameters passed to the `params` are available in `stack`.
    You can use custom paramter names, but keep in mind that numeric keys and
    string keys prefixed with a single underscore are reserved.
    License
    -------
    Copyright (C) 2012 Declan White
    Permission is hereby granted, free of charge, to any person obtaining a
    @@ -34,249 +89,446 @@
    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")
    do
    local type = rawtype or type
    local rawget = rawget
    local rawset = rawset
    local next = rawnext or next
    local getmetatable = debug and debug.getmetatable or getmetatable
    local setmetatable = debug and debug.setmetatable or setmetatable
    local debug_getupvalue = debug and debug.getupvalue or nil
    local debug_setupvalue = debug and debug.setupvalue or nil
    local debug_upvalueid = debug and debug.upvalueid or nil
    local debug_upvaluejoin = debug and debug.upvaluejoin or nil
    local unpack = unpack
    local table = table
    table.deepcopy_copyfunc_list = {
    --["type"] = function(stack, orig, copy, state, temp1, temp2, temp..., tempN)
    --
    -- -- When complete:
    -- state = true
    --
    -- -- Store temporary variables between iterations using these:
    -- -- (Note: you MUST NOT call these AFTER recurse)
    -- stack:_push(tempN+1, tempN+2, tempN+..., tempN+M)
    -- stack:_pop(K)
    -- -- K is the number to pop.
    -- -- If you wanted to pop two from the last state and push four new ones:
    -- stack:_pop(2)
    -- stack:_push('t', 'e', 's', 't')
    --
    -- -- To copy a child value:
    -- -- (Note: any calls to push or pop MUST be BEFORE a call to this)
    -- state:recurse(childvalue_orig)
    -- -- This will leave two temp variables on the stack for the next iteration
    -- -- .., childvalue_orig, childvalue_copy
    -- -- which are available via the varargs (temp...)
    -- -- (Note: the copy may be nil if it was not copied (because caller
    -- -- specified it not to be)).
    -- -- You can only call this once per iteration.
    --
    -- -- Return like this:
    -- -- (Temp variables are not part of the return list due to optimisation.)
    -- return copy, state
    --
    --end,
    _plainolddata = function(stack, orig, copy, state)
    return orig, true
    end,
    ["table"] = function(stack, orig, copy, state, arg1, arg2, arg3, arg4)
    local orig_prevkey, grabkey = nil, false
    if state == nil then -- 'init'
    -- Initial state, check for metatable, or get first key
    -- orig, copy:nil, state
    copy = stack[orig]
    if copy ~= nil then -- Check if already copied
    return copy, true
    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
    copy = {} -- Would be nice if you could preallocate sizes!
    stack[orig] = copy
    local orig_meta = getmetatable(orig)
    if orig_meta ~= nil then -- This table has a metatable, copy it
    if not stack.metatable_immutable then
    stack:_recurse(orig_meta)
    return copy, 'metatable'
    else
    setmetatable(copy, orig_meta)
    end
    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"
    )
    -- No metatable, go straight to copying key-value pairs
    orig_prevkey = nil -- grab first key
    grabkey = true --goto grabkey
    elseif state == 'metatable' then
    -- Metatable has been copied, set it and get first key
    -- orig, copy:{}, state, metaorig, metacopy
    local copy_meta = arg2--select(2, ...)
    stack:_pop(2)

    if copy_meta ~= nil then
    setmetatable(copy, copy_meta)
    end

    -- Now start copying key-value pairs
    orig_prevkey = nil -- grab first key
    grabkey = true --goto grabkey
    elseif state == 'key' then
    -- Key has been copied, now copy value
    -- orig, copy:{}, state, keyorig, keycopy
    local orig_key = arg1--select(1, ...)
    local copy_key = arg2--select(2, ...)

    if copy_key ~= nil then
    -- leave keyorig and keycopy on the stack
    local orig_value = rawget(orig, orig_key)
    stack:_recurse(orig_value)
    return copy, 'value'
    else -- key not copied? move onto next
    stack:_pop(2) -- pop keyorig, keycopy
    orig_prevkey = orig_key
    grabkey = true--goto grabkey
    end
    elseif state == 'value' then
    -- Value has been copied, set it and get next key
    -- orig, copy:{}, state, keyorig, keycopy, valueorig, valuecopy
    local orig_key = arg1--select(1, ...)
    local copy_key = arg2--select(2, ...)
    --local orig_value = arg3--select(3, ...)
    local copy_value = arg4--select(4, ...)
    stack:_pop(4)

    if copy_value ~= nil then
    rawset(copy, copy_key, copy_value)
    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!
    -- Grab next key to copy
    orig_prevkey = orig_key
    grabkey = true --goto grabkey
    end
    --return
    --::grabkey::
    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"
    )
    local orig_key, orig_value = next(orig, orig_prevkey)
    if orig_key ~= nil then
    stack:_recurse(orig_key) -- Copy key
    return copy, 'key'
    else
    stack[stack_ptr+STATE] = 'done' dbg('set', stack_ptr+STATE, "state")
    return copy, true -- Key is nil, copying of table is complete
    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")
    return
    end,
    ["function"] = function(stack, orig, copy, state, arg1, arg2, arg3)
    local grabupvalue, grabupvalue_idx = false, nil
    if state == nil then
    -- .., orig, copy, state
    copy = stack[orig]
    if copy ~= nil then
    return copy, true
    elseif stack.function_immutable then
    copy = orig
    return copy, true
    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
    copy = loadstring(string.dump(orig), nil, nil, stack.function_env)
    stack[orig] = copy

    stack_top = stack_top+1
    stack[stack_ptr+PART_SIZE+0] = 1
    grabupvalue = true
    if debug_getupvalue ~= nil and debug_setupvalue ~= nil then
    grabupvalue = true
    grabupvalue_idx = 1
    else
    -- No way to get/set upvalues!
    return copy, true
    end
    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
    -- .., orig, copy, state, uvidx, uvvalueorig, uvvaluecopy
    local orig_upvalue_idx = arg1
    --local orig_upvalue_value = arg2
    local copy_upvalue_value = arg3
    stack:_pop(3)

    debug.setupvalue(this_copy, upvalue_idx, upvalue_value_copy)
    debug_setupvalue(copy, orig_upvalue_idx, copy_upvalue_value)

    stack[stack_ptr+PART_SIZE] = upvalue_idx+1
    grabupvalue_idx = orig_upvalue_idx+1
    stack:_push(grabupvalue_idx)
    grabupvalue = true
    end
    if grabupvalue then
    -- .., orig, copy, retto, state, uvidx
    local upvalue_idx_curr = stack[stack_ptr+PART_SIZE+0]
    local upvalue_idx_curr = grabupvalue_idx
    for upvalue_idx = upvalue_idx_curr, math.huge do
    local upvalue_name, upvalue_value_orig = debug.getupvalue(this_orig, upvalue_idx)
    local upvalue_name, upvalue_value_orig = debug_getupvalue(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
    local upvalue_handled = false
    if not stack.function_upvalue_isolate and debug_upvalueid ~= nil and debug_upvaluejoin ~= nil then
    local upvalue_uid = debug.upvalueid(orig, upvalue_idx)
    -- Attempting to store an upvalueid of a function as a child of root is UB!
    local other_orig = stack[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 = stack[other_orig]
    debug_upvaluejoin(
    copy, upvalue_idx,
    other_copy, other_upvalue_idx
    )
    break
    end
    end
    upvalue_handled = true
    else
    stack[upvalue_uid] = orig
    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
    if not stack.function_upvalue_dontcopy and not upvalue_handled and upvalue_value_orig ~= nil then
    stack:_recurse(upvalue_value_orig)
    return copy, 'upvalue'
    end
    else
    stack_top = stack_top-1 -- pop uvidx
    stack[stack_ptr+STATE] = 'done' dbg('set', stack_ptr+3, "state")
    break
    stack:_pop(1) -- pop uvidx
    return copy, true
    end
    end
    end
    end,
    ["userdata"] = nil,
    ["lightuserdata"] = nil,
    ["thread"] = nil,
    }
    table.deepcopy_copyfunc_list["number" ] = table.deepcopy_copyfunc_list._plainolddata
    table.deepcopy_copyfunc_list["string" ] = table.deepcopy_copyfunc_list._plainolddata
    table.deepcopy_copyfunc_list["boolean"] = table.deepcopy_copyfunc_list._plainolddata
    -- `nil` should never be encounted... but just in case:
    table.deepcopy_copyfunc_list["nil" ] = table.deepcopy_copyfunc_list._plainolddata

    do
    local ORIG, COPY, RETTO, STATE, SIZE = 0, 1, 2, 3, 4
    function table.deepcopy_push(...)
    local arg_list_len = select('#', ...)
    local stack_offset = stack._top+1
    for arg_i = 1, arg_list_len do
    stack[stack_offset+arg_i] = select(arg_i, ...)
    end
    stack._top = stack_top+arg_list_len
    end
    function table.deepcopy_pop(stack, count)
    stack._top = stack._top-count
    end
    function table.deepcopy_recurse(stack, orig)
    local retto = stack._ptr
    local stack_top = stack._top
    local stack_ptr = stack_top+1
    stack._top = stack_top+SIZE
    stack._ptr = stack_ptr
    stack[stack_ptr+ORIG ] = orig
    stack[stack_ptr+COPY ] = nil
    stack[stack_ptr+RETTO] = retto
    stack[stack_ptr+STATE] = nil
    end
    function table.deepcopy(root, params, customcopyfunc_list)
    local stack = params or {}
    --orig,copy,retto,state,[temp...,] partorig,partcopy,partretoo,partstate
    stack[1+ORIG ] = root stack[1+COPY ] = nil
    stack[1+RETTO] = nil stack[1+STATE] = nil
    stack._ptr = 1 stack._top = 4
    stack._push = table.deepcopy_push stack._pop = table.deepcopy_pop
    stack._recurse = table.deepcopy_recurse
    --[[local stack_dbg do -- debug
    stack_dbg = stack
    stack = setmetatable({}, {
    __index = stack_dbg,
    __newindex = function(t, k, v)
    stack_dbg[k] = v
    if tonumber(k) then
    local stack = stack_dbg
    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 "")..(stack_i == k 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
    .. ""
    )
    io.read()
    elseif false then
    io.stdout:write(("stack.%s = %s"):format(
    k,
    (
    (type(v) == 'table' or type(v) == 'function')
    and string.gsub(tostring(v), "^.-(%x%x%x%x%x%x%x%x)$", "<%1>")
    or tostring(v)
    )
    ))
    io.read()
    end
    end,
    })
    end]]
    local copyfunc_list = table.deepcopy_copyfunc_list
    repeat
    local stack_ptr = stack._ptr
    local this_orig = stack[stack_ptr+ORIG]
    local this_copy, this_state
    stack[0] = stack[0]
    if stack.value_ignore and stack.value_ignore[this_orig] then
    this_copy = nil
    this_state = true --goto valuefound
    else
    if stack.value_translate then
    this_copy = stack.value_translate[this_orig]
    if this_copy ~= nil then
    this_state = true --goto valuefound
    end
    end
    if not this_state then
    local this_orig_type = type(this_orig)
    local copyfunc = (
    customcopyfunc_list and customcopyfunc_list[this_orig_type]
    or copyfunc_list[this_orig_type]
    or error(("cannot copy type %q"):format(this_orig_type), 2)
    )
    this_copy, this_state = copyfunc(
    stack,
    this_orig,
    stack[stack_ptr+COPY],
    unpack(stack--[[_dbg]], stack_ptr+STATE, stack._top)
    )
    end
    end
    stack[stack_ptr+COPY] = this_copy
    --::valuefound::
    if this_state == true then
    local retto = stack[stack_ptr+RETTO]
    stack._top = stack_ptr+1 -- pop retto, state, temp...
    -- Leave orig and copy on stack for parent object
    stack_ptr = retto -- return to parent's stack frame
    stack._ptr = stack_ptr
    else
    stack[stack_ptr+STATE] = this_state
    end
    until stack_ptr == nil
    return stack[1+COPY]
    end
    end
    end


    function main(...)
    -- cyclic table
    local v
    do
    v = {
    'a',
    cactus = {
    6,
    abc = { 22 },
    },
    'c'
    }
    v.cactus.abc.q = v
    end
    --[[setfenv(table.deepcopy,
    setmetatable({}, {
    __index = function(t,k ) error(("_G[%q]!" ):format(tostring(k) ), 2) end,
    __newindex = function(t,k,v) error(("_G[%q] = %q!"):format(tostring(k),tostring(v)), 2) end,
    })
    )]]
    local nv = table.deepcopy(v)
    print(stringify(nv))

    -- common upvalue
    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
    nv = table.deepcopy(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
    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
    stack[#stack+1] = string.rep(spacing_h, space_n-1).."}"
    end
    dbg_its = dbg_its-1
    elseif this_type == "nil" then
    stack[#stack+1] = "nil"
    else
    stack[#stack+1] = this_type.."<"..tostring(this)..">"
    end
    return stack[1+COPY]
    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(...)
    58 changes: 1 addition & 57 deletions deepcopy_test.lua
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,4 @@


    function main(...)
    local v
    do
    @@ -27,61 +26,6 @@ function main(...)
    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
    require"stringify"

    main(...)
    57 changes: 57 additions & 0 deletions stringify.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,57 @@

    _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
  3. Deco revised this gist Oct 31, 2012. 2 changed files with 87 additions and 87 deletions.
    87 changes: 0 additions & 87 deletions deepcopy.lua
    Original file line number Diff line number Diff line change
    @@ -280,90 +280,3 @@ function copy(root, dbg, env)
    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(...)

    87 changes: 87 additions & 0 deletions deepcopy_test.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,87 @@


    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(...)
  4. Deco created this gist Oct 31, 2012.
    369 changes: 369 additions & 0 deletions deepcopy.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,369 @@
    --[[ 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(...)