From a58552fc7959227563ee4a386085d77e44f2ff86 Mon Sep 17 00:00:00 2001 From: Javier Leija Date: Fri, 16 Aug 2019 18:35:37 -0500 Subject: [PATCH] liluat.private.clone_table: added support for cloning cyclical tables (and their metatables). Fixes stack overflow error due to recursion. --- liluat.lua | 26 ++++++++++++++------------ spec/liluat_spec.lua | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/liluat.lua b/liluat.lua index 0e9e194..1028aa0 100644 --- a/liluat.lua +++ b/liluat.lua @@ -63,19 +63,21 @@ end liluat.private.escape_pattern = escape_pattern -- recursively copy a table -local function clone_table(table) - local clone = {} - - for key, value in pairs(table) do - if type(value) == "table" then - clone[key] = clone_table(value) - else - clone[key] = value - end - end - - return clone +-- adapted from https://gist.github.com/tylerneylon/81333721109155b2d244, copy3() recursive function +local function clone_table(obj, seen) + -- Handle non-tables and previously-seen tables. + if type(obj) ~= 'table' then return obj end + if seen and seen[obj] then return seen[obj] end + + -- New table; mark it as seen an copy recursively. + local s = seen or {} + local res = {} + s[obj] = res + setmetatable(res, clone_table(getmetatable(obj), s)) + for k, v in pairs(obj) do res[clone_table(k, s)] = clone_table(v, s) end + return res end + liluat.private.clone_table = clone_table -- recursively merge two tables, the second one has precedence diff --git a/spec/liluat_spec.lua b/spec/liluat_spec.lua index c5b6350..a244257 100644 --- a/spec/liluat_spec.lua +++ b/spec/liluat_spec.lua @@ -188,6 +188,31 @@ Hello, <world>! assert.not_equal(table.a, clone.a) assert.not_equal(table.a.c, clone.a.c) end) + it("should clone a self-referencing/cyclical table (with metatable)", function () + local table = { + a = { + b = 1, + c = { + d = 2 + } + }, + e = 3 + } + table.f = table + table.a.c.g = table.a + setmetatable(table, table) + + local clone = liluat.private.clone_table(table) + + assert.same(table, clone) + assert.same(getmetatable(table), getmetatable(clone)) + assert.not_equal(table, clone) + assert.not_equal(table.a, clone.a) + assert.not_equal(table.a.c, clone.a.c) + assert.not_equal(table.f, clone.f) + assert.not_equal(table.a.c.g, clone.a.c.g) + assert.not_equal(getmetatable(table).a.c.g, getmetatable(clone).a.c.g) + end) end) describe("merge_tables", function ()