From ce4babb19289f952cf440b30b38ba127e78e810a Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 30 Jun 2010 00:36:25 +0000 Subject: [PATCH] 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. --- aegisub/automation/include/utils-auto4.lua | 29 ++++++++++----- .../tests/test-tablecopy-recursive.lua | 37 +++++++++++++++++++ 2 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 aegisub/automation/tests/test-tablecopy-recursive.lua diff --git a/aegisub/automation/include/utils-auto4.lua b/aegisub/automation/include/utils-auto4.lua index 161b8edb8..368163162 100644 --- a/aegisub/automation/include/utils-auto4.lua +++ b/aegisub/automation/include/utils-auto4.lua @@ -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. Redistribution and use in source and binary forms, with or without @@ -39,21 +39,30 @@ end copy_line = table.copy -- 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) -- Table to hold subtables already copied, to avoid circular references causing infinite recursion local circular = {} local function do_copy(oldtab) - local newtab = {} - for key, val in pairs(newtab) do - if type(val) == "table" then - if not circular[val] then - circular[val] = do_copy(val) + -- 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 = {} + -- 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 + newtab[key] = do_copy(val) + else + newtab[key] = val end - newtab[key] = circular[val] - else - newtab[key] = val end + return newtab end end return do_copy(srctab) diff --git a/aegisub/automation/tests/test-tablecopy-recursive.lua b/aegisub/automation/tests/test-tablecopy-recursive.lua new file mode 100644 index 000000000..3a7fa98bb --- /dev/null +++ b/aegisub/automation/tests/test-tablecopy-recursive.lua @@ -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)