Quantcast
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 = {

        _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
                    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
                -- 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

                -- Grab next key to copy
                orig_prevkey = orig_key
                grabkey = true --goto grabkey
            end
            --return
            --::grabkey::
            if grabkey then
                local orig_key, orig_value = next(orig, orig_prevkey)
                if orig_key ~= nil then
                    stack:_recurse(orig_key) -- Copy key
                    return copy, 'key'
                else
                    return copy, true -- Key is nil, copying of table is complete
                end
            end
            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
                    copy = loadstring(string.dump(orig), nil, nil, stack.function_env)
                    stack[orig] = copy

                    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, state, uvidx, uvvalueorig, uvvaluecopy
                local orig_upvalue_idx   = arg1
              --local orig_upvalue_value = arg2
                local copy_upvalue_value = arg3
                stack:_pop(3)

                debug_setupvalue(copy, orig_upvalue_idx, copy_upvalue_value)

                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 = grabupvalue_idx
                for upvalue_idx = upvalue_idx_curr, math.huge do
                    local upvalue_name, upvalue_value_orig = debug_getupvalue(orig, upvalue_idx)
                    if upvalue_name ~= nil then
                        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
                        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:_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 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