Update table.copy_deep function to correctly handle self-referencing tables and tables with circular references. Doesn't handle tables with meta-tables overriding regular field get/set behaviour but that isn't intended either way. Also add a test of this.

Originally committed to SVN as r4645.
This commit is contained in:
Niels Martin Hansen 2010-06-30 00:36:25 +00:00
parent 816b12cec6
commit ce4babb192
2 changed files with 56 additions and 10 deletions

View file

@ -1,5 +1,5 @@
--[[ --[[
Copyright (c) 2005-2006, Niels Martin Hansen, Rodrigo Braz Monteiro Copyright (c) 2005-2010, Niels Martin Hansen, Rodrigo Braz Monteiro
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@ -39,22 +39,31 @@ end
copy_line = table.copy copy_line = table.copy
-- Make a deep copy of a table -- Make a deep copy of a table
-- This will do infinite recursion if there's any circular references. (Eg. if you try to copy _G) -- Retains equality of table references inside the copy and handles self-referencing structures
function table.copy_deep(srctab) function table.copy_deep(srctab)
-- Table to hold subtables already copied, to avoid circular references causing infinite recursion -- Table to hold subtables already copied, to avoid circular references causing infinite recursion
local circular = {} local circular = {}
local function do_copy(oldtab) local function do_copy(oldtab)
-- Check if we know the source already
if circular[oldtab] then
-- Use already-made copy
return circular[oldtab]
else
-- Prepare a new table to copy into
local newtab = {} local newtab = {}
for key, val in pairs(newtab) do -- Register it as known
circular[oldtab] = newtab
-- Copy fields
for key, val in pairs(oldtab) do
-- Copy tables recursively, everything else normally
if type(val) == "table" then if type(val) == "table" then
if not circular[val] then newtab[key] = do_copy(val)
circular[val] = do_copy(val)
end
newtab[key] = circular[val]
else else
newtab[key] = val newtab[key] = val
end end
end end
return newtab
end
end end
return do_copy(srctab) return do_copy(srctab)
end end

View file

@ -0,0 +1,37 @@
script_name = "Test table.copy_deep"
script_description = "Tests the Auto4 Lua utils.lua table.copy_deep function"
script_author = "Niels Martin Hansen"
include "utils.lua"
function test_tablecopy_deep()
local function test_table(tab, desc)
local l = aegisub.log
l("--- %15s -------------\n", desc)
l("tab.a = %d\n", tab.a)
l("type(tab.b) = %s\n", type(tab.b))
l("tab.b.a = %s\n", tab.b.a)
l("tab.c==tab.b ? %d\n", (tab.c==tab.b) and 1 or 0)
l("type(tab.b.b) = %s\n", type(tab.b.b))
l("type(tab.d) = %s\n", type(tab.d))
l("tab.d.a == tab.d ? %d\n", (tab.d.a==tab.d) and 1 or 0)
l("tab.e == tab ? %d\n", (tab.e==tab) and 1 or 0)
l("\n")
end
local orgtab = {}
orgtab.a = 1
orgtab.b = {}
orgtab.b.a = "hi"
orgtab.c = orgtab.b
orgtab.c.b = test_table
orgtab.d = {}
orgtab.d.a = orgtab.d
orgtab.e = orgtab
test_table(orgtab, "Original table")
local cpytab = table.copy_deep(orgtab)
test_table(cpytab, "Copied table")
end
aegisub.register_macro("TEST table.copy_deep", "", test_tablecopy_deep)