isaacscript-common 6.10.0 → 6.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/functions/deepCopy.lua +50 -14
- package/dist/functions/deepCopyTests.d.ts.map +1 -1
- package/dist/functions/deepCopyTests.lua +78 -21
- package/dist/functions/jsonHelpers.d.ts +6 -0
- package/dist/functions/jsonHelpers.d.ts.map +1 -1
- package/dist/functions/jsonHelpers.lua +9 -3
- package/dist/functions/log.d.ts.map +1 -1
- package/dist/functions/log.lua +3 -3
- package/dist/functions/mergeTests.lua +0 -4
- package/dist/lib/jsonLua.lua +388 -0
- package/package.json +1 -1
- package/src/functions/deepCopy.ts +56 -5
- package/src/functions/deepCopyTests.ts +146 -17
- package/src/functions/jsonHelpers.ts +9 -3
- package/src/functions/log.ts +4 -2
- package/src/functions/mergeTests.ts +0 -8
- package/src/lib/jsonLua.d.ts +10 -0
- package/src/lib/jsonLua.lua +388 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
--
|
|
2
|
+
-- json.lua
|
|
3
|
+
--
|
|
4
|
+
-- Copyright (c) 2020 rxi
|
|
5
|
+
--
|
|
6
|
+
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
7
|
+
-- this software and associated documentation files (the "Software"), to deal in
|
|
8
|
+
-- the Software without restriction, including without limitation the rights to
|
|
9
|
+
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
10
|
+
-- of the Software, and to permit persons to whom the Software is furnished to do
|
|
11
|
+
-- so, subject to the following conditions:
|
|
12
|
+
--
|
|
13
|
+
-- The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
-- copies or substantial portions of the Software.
|
|
15
|
+
--
|
|
16
|
+
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
-- SOFTWARE.
|
|
23
|
+
--
|
|
24
|
+
|
|
25
|
+
local json = { _version = "0.1.2" }
|
|
26
|
+
|
|
27
|
+
-------------------------------------------------------------------------------
|
|
28
|
+
-- Encode
|
|
29
|
+
-------------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
local encode
|
|
32
|
+
|
|
33
|
+
local escape_char_map = {
|
|
34
|
+
[ "\\" ] = "\\",
|
|
35
|
+
[ "\"" ] = "\"",
|
|
36
|
+
[ "\b" ] = "b",
|
|
37
|
+
[ "\f" ] = "f",
|
|
38
|
+
[ "\n" ] = "n",
|
|
39
|
+
[ "\r" ] = "r",
|
|
40
|
+
[ "\t" ] = "t",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
local escape_char_map_inv = { [ "/" ] = "/" }
|
|
44
|
+
for k, v in pairs(escape_char_map) do
|
|
45
|
+
escape_char_map_inv[v] = k
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
local function escape_char(c)
|
|
50
|
+
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
local function encode_nil(val)
|
|
55
|
+
return "null"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
local function encode_table(val, stack)
|
|
60
|
+
local res = {}
|
|
61
|
+
stack = stack or {}
|
|
62
|
+
|
|
63
|
+
-- Circular reference?
|
|
64
|
+
if stack[val] then error("circular reference") end
|
|
65
|
+
|
|
66
|
+
stack[val] = true
|
|
67
|
+
|
|
68
|
+
if rawget(val, 1) ~= nil or next(val) == nil then
|
|
69
|
+
-- Treat as array -- check keys are valid and it is not sparse
|
|
70
|
+
local n = 0
|
|
71
|
+
for k in pairs(val) do
|
|
72
|
+
if type(k) ~= "number" then
|
|
73
|
+
error("invalid table: mixed or invalid key types")
|
|
74
|
+
end
|
|
75
|
+
n = n + 1
|
|
76
|
+
end
|
|
77
|
+
if n ~= #val then
|
|
78
|
+
error("invalid table: sparse array")
|
|
79
|
+
end
|
|
80
|
+
-- Encode
|
|
81
|
+
for i, v in ipairs(val) do
|
|
82
|
+
table.insert(res, encode(v, stack))
|
|
83
|
+
end
|
|
84
|
+
stack[val] = nil
|
|
85
|
+
return "[" .. table.concat(res, ",") .. "]"
|
|
86
|
+
|
|
87
|
+
else
|
|
88
|
+
-- Treat as an object
|
|
89
|
+
for k, v in pairs(val) do
|
|
90
|
+
if type(k) ~= "string" then
|
|
91
|
+
error("invalid table: mixed or invalid key types")
|
|
92
|
+
end
|
|
93
|
+
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
|
94
|
+
end
|
|
95
|
+
stack[val] = nil
|
|
96
|
+
return "{" .. table.concat(res, ",") .. "}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
local function encode_string(val)
|
|
102
|
+
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
local function encode_number(val)
|
|
107
|
+
-- Check for NaN, -inf and inf
|
|
108
|
+
if val ~= val or val <= -math.huge or val >= math.huge then
|
|
109
|
+
error("unexpected number value '" .. tostring(val) .. "'")
|
|
110
|
+
end
|
|
111
|
+
return string.format("%.14g", val)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
local type_func_map = {
|
|
116
|
+
[ "nil" ] = encode_nil,
|
|
117
|
+
[ "table" ] = encode_table,
|
|
118
|
+
[ "string" ] = encode_string,
|
|
119
|
+
[ "number" ] = encode_number,
|
|
120
|
+
[ "boolean" ] = tostring,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
encode = function(val, stack)
|
|
125
|
+
local t = type(val)
|
|
126
|
+
local f = type_func_map[t]
|
|
127
|
+
if f then
|
|
128
|
+
return f(val, stack)
|
|
129
|
+
end
|
|
130
|
+
error("unexpected type '" .. t .. "'")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
function json.encode(val)
|
|
135
|
+
return ( encode(val) )
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
-------------------------------------------------------------------------------
|
|
140
|
+
-- Decode
|
|
141
|
+
-------------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
local parse
|
|
144
|
+
|
|
145
|
+
local function create_set(...)
|
|
146
|
+
local res = {}
|
|
147
|
+
for i = 1, select("#", ...) do
|
|
148
|
+
res[ select(i, ...) ] = true
|
|
149
|
+
end
|
|
150
|
+
return res
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
local space_chars = create_set(" ", "\t", "\r", "\n")
|
|
154
|
+
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
|
155
|
+
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
|
156
|
+
local literals = create_set("true", "false", "null")
|
|
157
|
+
|
|
158
|
+
local literal_map = {
|
|
159
|
+
[ "true" ] = true,
|
|
160
|
+
[ "false" ] = false,
|
|
161
|
+
[ "null" ] = nil,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
local function next_char(str, idx, set, negate)
|
|
166
|
+
for i = idx, #str do
|
|
167
|
+
if set[str:sub(i, i)] ~= negate then
|
|
168
|
+
return i
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
return #str + 1
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
local function decode_error(str, idx, msg)
|
|
176
|
+
local line_count = 1
|
|
177
|
+
local col_count = 1
|
|
178
|
+
for i = 1, idx - 1 do
|
|
179
|
+
col_count = col_count + 1
|
|
180
|
+
if str:sub(i, i) == "\n" then
|
|
181
|
+
line_count = line_count + 1
|
|
182
|
+
col_count = 1
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
local function codepoint_to_utf8(n)
|
|
190
|
+
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
|
191
|
+
local f = math.floor
|
|
192
|
+
if n <= 0x7f then
|
|
193
|
+
return string.char(n)
|
|
194
|
+
elseif n <= 0x7ff then
|
|
195
|
+
return string.char(f(n / 64) + 192, n % 64 + 128)
|
|
196
|
+
elseif n <= 0xffff then
|
|
197
|
+
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
|
198
|
+
elseif n <= 0x10ffff then
|
|
199
|
+
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
|
200
|
+
f(n % 4096 / 64) + 128, n % 64 + 128)
|
|
201
|
+
end
|
|
202
|
+
error( string.format("invalid unicode codepoint '%x'", n) )
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
local function parse_unicode_escape(s)
|
|
207
|
+
local n1 = tonumber( s:sub(1, 4), 16 )
|
|
208
|
+
local n2 = tonumber( s:sub(7, 10), 16 )
|
|
209
|
+
-- Surrogate pair?
|
|
210
|
+
if n2 then
|
|
211
|
+
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
|
212
|
+
else
|
|
213
|
+
return codepoint_to_utf8(n1)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
local function parse_string(str, i)
|
|
219
|
+
local res = ""
|
|
220
|
+
local j = i + 1
|
|
221
|
+
local k = j
|
|
222
|
+
|
|
223
|
+
while j <= #str do
|
|
224
|
+
local x = str:byte(j)
|
|
225
|
+
|
|
226
|
+
if x < 32 then
|
|
227
|
+
decode_error(str, j, "control character in string")
|
|
228
|
+
|
|
229
|
+
elseif x == 92 then -- `\`: Escape
|
|
230
|
+
res = res .. str:sub(k, j - 1)
|
|
231
|
+
j = j + 1
|
|
232
|
+
local c = str:sub(j, j)
|
|
233
|
+
if c == "u" then
|
|
234
|
+
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
|
235
|
+
or str:match("^%x%x%x%x", j + 1)
|
|
236
|
+
or decode_error(str, j - 1, "invalid unicode escape in string")
|
|
237
|
+
res = res .. parse_unicode_escape(hex)
|
|
238
|
+
j = j + #hex
|
|
239
|
+
else
|
|
240
|
+
if not escape_chars[c] then
|
|
241
|
+
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
|
242
|
+
end
|
|
243
|
+
res = res .. escape_char_map_inv[c]
|
|
244
|
+
end
|
|
245
|
+
k = j + 1
|
|
246
|
+
|
|
247
|
+
elseif x == 34 then -- `"`: End of string
|
|
248
|
+
res = res .. str:sub(k, j - 1)
|
|
249
|
+
return res, j + 1
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
j = j + 1
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
decode_error(str, i, "expected closing quote for string")
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
local function parse_number(str, i)
|
|
260
|
+
local x = next_char(str, i, delim_chars)
|
|
261
|
+
local s = str:sub(i, x - 1)
|
|
262
|
+
local n = tonumber(s)
|
|
263
|
+
if not n then
|
|
264
|
+
decode_error(str, i, "invalid number '" .. s .. "'")
|
|
265
|
+
end
|
|
266
|
+
return n, x
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
local function parse_literal(str, i)
|
|
271
|
+
local x = next_char(str, i, delim_chars)
|
|
272
|
+
local word = str:sub(i, x - 1)
|
|
273
|
+
if not literals[word] then
|
|
274
|
+
decode_error(str, i, "invalid literal '" .. word .. "'")
|
|
275
|
+
end
|
|
276
|
+
return literal_map[word], x
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
local function parse_array(str, i)
|
|
281
|
+
local res = {}
|
|
282
|
+
local n = 1
|
|
283
|
+
i = i + 1
|
|
284
|
+
while 1 do
|
|
285
|
+
local x
|
|
286
|
+
i = next_char(str, i, space_chars, true)
|
|
287
|
+
-- Empty / end of array?
|
|
288
|
+
if str:sub(i, i) == "]" then
|
|
289
|
+
i = i + 1
|
|
290
|
+
break
|
|
291
|
+
end
|
|
292
|
+
-- Read token
|
|
293
|
+
x, i = parse(str, i)
|
|
294
|
+
res[n] = x
|
|
295
|
+
n = n + 1
|
|
296
|
+
-- Next token
|
|
297
|
+
i = next_char(str, i, space_chars, true)
|
|
298
|
+
local chr = str:sub(i, i)
|
|
299
|
+
i = i + 1
|
|
300
|
+
if chr == "]" then break end
|
|
301
|
+
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
|
302
|
+
end
|
|
303
|
+
return res, i
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
local function parse_object(str, i)
|
|
308
|
+
local res = {}
|
|
309
|
+
i = i + 1
|
|
310
|
+
while 1 do
|
|
311
|
+
local key, val
|
|
312
|
+
i = next_char(str, i, space_chars, true)
|
|
313
|
+
-- Empty / end of object?
|
|
314
|
+
if str:sub(i, i) == "}" then
|
|
315
|
+
i = i + 1
|
|
316
|
+
break
|
|
317
|
+
end
|
|
318
|
+
-- Read key
|
|
319
|
+
if str:sub(i, i) ~= '"' then
|
|
320
|
+
decode_error(str, i, "expected string for key")
|
|
321
|
+
end
|
|
322
|
+
key, i = parse(str, i)
|
|
323
|
+
-- Read ':' delimiter
|
|
324
|
+
i = next_char(str, i, space_chars, true)
|
|
325
|
+
if str:sub(i, i) ~= ":" then
|
|
326
|
+
decode_error(str, i, "expected ':' after key")
|
|
327
|
+
end
|
|
328
|
+
i = next_char(str, i + 1, space_chars, true)
|
|
329
|
+
-- Read value
|
|
330
|
+
val, i = parse(str, i)
|
|
331
|
+
-- Set
|
|
332
|
+
res[key] = val
|
|
333
|
+
-- Next token
|
|
334
|
+
i = next_char(str, i, space_chars, true)
|
|
335
|
+
local chr = str:sub(i, i)
|
|
336
|
+
i = i + 1
|
|
337
|
+
if chr == "}" then break end
|
|
338
|
+
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
|
339
|
+
end
|
|
340
|
+
return res, i
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
local char_func_map = {
|
|
345
|
+
[ '"' ] = parse_string,
|
|
346
|
+
[ "0" ] = parse_number,
|
|
347
|
+
[ "1" ] = parse_number,
|
|
348
|
+
[ "2" ] = parse_number,
|
|
349
|
+
[ "3" ] = parse_number,
|
|
350
|
+
[ "4" ] = parse_number,
|
|
351
|
+
[ "5" ] = parse_number,
|
|
352
|
+
[ "6" ] = parse_number,
|
|
353
|
+
[ "7" ] = parse_number,
|
|
354
|
+
[ "8" ] = parse_number,
|
|
355
|
+
[ "9" ] = parse_number,
|
|
356
|
+
[ "-" ] = parse_number,
|
|
357
|
+
[ "t" ] = parse_literal,
|
|
358
|
+
[ "f" ] = parse_literal,
|
|
359
|
+
[ "n" ] = parse_literal,
|
|
360
|
+
[ "[" ] = parse_array,
|
|
361
|
+
[ "{" ] = parse_object,
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
parse = function(str, idx)
|
|
366
|
+
local chr = str:sub(idx, idx)
|
|
367
|
+
local f = char_func_map[chr]
|
|
368
|
+
if f then
|
|
369
|
+
return f(str, idx)
|
|
370
|
+
end
|
|
371
|
+
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
function json.decode(str)
|
|
376
|
+
if type(str) ~= "string" then
|
|
377
|
+
error("expected argument of type string, got " .. type(str))
|
|
378
|
+
end
|
|
379
|
+
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
|
380
|
+
idx = next_char(str, idx, space_chars, true)
|
|
381
|
+
if idx <= #str then
|
|
382
|
+
decode_error(str, idx, "trailing garbage")
|
|
383
|
+
end
|
|
384
|
+
return res
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
return json
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@ import { TSTLClass } from "../types/private/TSTLClass";
|
|
|
8
8
|
import { isArray } from "./array";
|
|
9
9
|
import { getEnumValues } from "./enums";
|
|
10
10
|
import { getIsaacAPIClassName } from "./isaacAPIClass";
|
|
11
|
-
import { log
|
|
11
|
+
import { log } from "./log";
|
|
12
12
|
import {
|
|
13
13
|
copyIsaacAPIClass,
|
|
14
14
|
deserializeIsaacAPIClass,
|
|
@@ -209,6 +209,11 @@ function deepCopyDefaultMap(
|
|
|
209
209
|
traversalDescription: string,
|
|
210
210
|
insideMap: boolean,
|
|
211
211
|
) {
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
213
|
+
if (SAVE_DATA_MANAGER_DEBUG) {
|
|
214
|
+
log("deepCopy is copying a DefaultMap.");
|
|
215
|
+
}
|
|
216
|
+
|
|
212
217
|
const constructorArg = isDefaultMap(defaultMap)
|
|
213
218
|
? defaultMap.getConstructorArg()
|
|
214
219
|
: undefined; // The undefined case is handled explicitly in the "getNewDefaultMap" function.
|
|
@@ -291,7 +296,7 @@ function getNewDefaultMap(
|
|
|
291
296
|
serializationType: SerializationType,
|
|
292
297
|
traversalDescription: string,
|
|
293
298
|
constructorArg: unknown,
|
|
294
|
-
) {
|
|
299
|
+
): DefaultMap<AnyNotNil, unknown> | LuaMap<AnyNotNil, unknown> {
|
|
295
300
|
switch (serializationType) {
|
|
296
301
|
case SerializationType.NONE: {
|
|
297
302
|
// eslint-disable-next-line isaacscript/no-invalid-default-map
|
|
@@ -336,6 +341,11 @@ function deepCopyMap(
|
|
|
336
341
|
traversalDescription: string,
|
|
337
342
|
insideMap: boolean,
|
|
338
343
|
) {
|
|
344
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
345
|
+
if (SAVE_DATA_MANAGER_DEBUG) {
|
|
346
|
+
log("deepCopy is copying a Map.");
|
|
347
|
+
}
|
|
348
|
+
|
|
339
349
|
let newMap: Map<AnyNotNil, unknown> | LuaMap<AnyNotNil, unknown>;
|
|
340
350
|
if (serializationType === SerializationType.SERIALIZE) {
|
|
341
351
|
// Since we are serializing, the new object will be a Lua table.
|
|
@@ -384,6 +394,11 @@ function deepCopySet(
|
|
|
384
394
|
traversalDescription: string,
|
|
385
395
|
insideMap: boolean,
|
|
386
396
|
) {
|
|
397
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
398
|
+
if (SAVE_DATA_MANAGER_DEBUG) {
|
|
399
|
+
log("deepCopy is copying a Set.");
|
|
400
|
+
}
|
|
401
|
+
|
|
387
402
|
let newSet: Set<AnyNotNil> | LuaMap<AnyNotNil, string>;
|
|
388
403
|
if (serializationType === SerializationType.SERIALIZE) {
|
|
389
404
|
// For serialization purposes, we represent a `Set` as a table with keys that match the
|
|
@@ -405,7 +420,7 @@ function deepCopySet(
|
|
|
405
420
|
// Differentiating between the two types looks superfluous but is necessary for TSTL to produce
|
|
406
421
|
// the proper set method call.
|
|
407
422
|
if (isTSTLSet(newSet)) {
|
|
408
|
-
// We should never be serializing an object of type Set
|
|
423
|
+
// We should never be serializing an object of type `Set`.
|
|
409
424
|
error(
|
|
410
425
|
"The deep copy function cannot convert number keys to strings for a Set.",
|
|
411
426
|
);
|
|
@@ -433,6 +448,11 @@ function deepCopyTSTLClass(
|
|
|
433
448
|
traversalDescription: string,
|
|
434
449
|
insideMap: boolean,
|
|
435
450
|
) {
|
|
451
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
452
|
+
if (SAVE_DATA_MANAGER_DEBUG) {
|
|
453
|
+
log("deepCopy is copying a TSTL class.");
|
|
454
|
+
}
|
|
455
|
+
|
|
436
456
|
let newClass: TSTLClass | LuaMap<AnyNotNil, unknown>;
|
|
437
457
|
if (serializationType === SerializationType.SERIALIZE) {
|
|
438
458
|
// Since we are serializing, the new object will be a Lua table.
|
|
@@ -467,6 +487,11 @@ function deepCopyArray(
|
|
|
467
487
|
traversalDescription: string,
|
|
468
488
|
insideMap: boolean,
|
|
469
489
|
) {
|
|
490
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
491
|
+
if (SAVE_DATA_MANAGER_DEBUG) {
|
|
492
|
+
log("deepCopy is copying an array.");
|
|
493
|
+
}
|
|
494
|
+
|
|
470
495
|
const newArray: unknown[] = [];
|
|
471
496
|
|
|
472
497
|
for (const value of array) {
|
|
@@ -488,6 +513,11 @@ function deepCopyNormalLuaTable(
|
|
|
488
513
|
traversalDescription: string,
|
|
489
514
|
insideMap: boolean,
|
|
490
515
|
) {
|
|
516
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
517
|
+
if (SAVE_DATA_MANAGER_DEBUG) {
|
|
518
|
+
log("deepCopy is copying a normal Lua table.");
|
|
519
|
+
}
|
|
520
|
+
|
|
491
521
|
const newTable = new LuaMap<AnyNotNil, unknown>();
|
|
492
522
|
const { entries, convertedNumberKeysToStrings } = getCopiedEntries(
|
|
493
523
|
luaMap,
|
|
@@ -535,10 +565,17 @@ function getCopiedEntries(
|
|
|
535
565
|
|
|
536
566
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
537
567
|
if (SAVE_DATA_MANAGER_DEBUG) {
|
|
538
|
-
logTable(entries);
|
|
539
568
|
entries.sort(twoDimensionalSort);
|
|
540
569
|
}
|
|
541
570
|
|
|
571
|
+
// During serialization, we brand some Lua tables with a special identifier to signify that it has
|
|
572
|
+
// keys that should be deserialized to numbers.
|
|
573
|
+
const convertStringKeysToNumbers =
|
|
574
|
+
serializationType === SerializationType.DESERIALIZE &&
|
|
575
|
+
entries.some(
|
|
576
|
+
([key]) => key === (SerializationBrand.OBJECT_WITH_NUMBER_KEYS as string),
|
|
577
|
+
);
|
|
578
|
+
|
|
542
579
|
const hasNumberKeys = entries.some(([key]) => isNumber(key));
|
|
543
580
|
const convertNumberKeysToStrings =
|
|
544
581
|
serializationType === SerializationType.SERIALIZE && hasNumberKeys;
|
|
@@ -560,7 +597,16 @@ function getCopiedEntries(
|
|
|
560
597
|
insideMap,
|
|
561
598
|
);
|
|
562
599
|
|
|
563
|
-
|
|
600
|
+
let keyToUse = key;
|
|
601
|
+
if (convertStringKeysToNumbers) {
|
|
602
|
+
const numberKey = tonumber(key);
|
|
603
|
+
if (numberKey !== undefined) {
|
|
604
|
+
keyToUse = numberKey;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (convertNumberKeysToStrings) {
|
|
608
|
+
keyToUse = tostring(key);
|
|
609
|
+
}
|
|
564
610
|
copiedEntries.push([keyToUse, newValue]);
|
|
565
611
|
}
|
|
566
612
|
|
|
@@ -600,6 +646,11 @@ function deepCopyUserdata(
|
|
|
600
646
|
serializationType: SerializationType,
|
|
601
647
|
traversalDescription: string,
|
|
602
648
|
) {
|
|
649
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
650
|
+
if (SAVE_DATA_MANAGER_DEBUG) {
|
|
651
|
+
log("deepCopy is copying userdata.");
|
|
652
|
+
}
|
|
653
|
+
|
|
603
654
|
const classType = getIsaacAPIClassName(value);
|
|
604
655
|
if (classType === undefined) {
|
|
605
656
|
error(
|