-
Notifications
You must be signed in to change notification settings - Fork 6
/
DeepCopy.lua
427 lines (409 loc) · 15.4 KB
/
DeepCopy.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
local addonName,addonTable = ...
local DA = LibStub("AceAddon-3.0"):GetAddon("Skillet")
--[[ deepcopy.lua
Deep-copy function for Lua - v0.2
==============================
- Does not overflow the stack.
- Maintains cyclic-references
- Copies metatables
- Maintains common upvalues between copied functions (for Lua 5.2 only)
TODO
----
- 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 = DA.deepcopy(orig)
copy = DA.deepcopy(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
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.
]]
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
DA.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
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,
}
DA.deepcopy_copyfunc_list["number" ] = DA.deepcopy_copyfunc_list._plainolddata
DA.deepcopy_copyfunc_list["string" ] = DA.deepcopy_copyfunc_list._plainolddata
DA.deepcopy_copyfunc_list["boolean"] = DA.deepcopy_copyfunc_list._plainolddata
-- `nil` should never be encounted... but just in case:
DA.deepcopy_copyfunc_list["nil" ] = DA.deepcopy_copyfunc_list._plainolddata
do
local ORIG, COPY, RETTO, STATE, SIZE = 0, 1, 2, 3, 4
function DA.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 DA.deepcopy_pop(stack, count)
stack._top = stack._top-count
end
function DA.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 DA.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 = DA.deepcopy_push stack._pop = DA.deepcopy_pop
stack._recurse = DA.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 = DA.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