isaacscript-common 6.10.2 → 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.
@@ -3,10 +3,8 @@ local __TS__TypeOf = ____lualib.__TS__TypeOf
3
3
  local Map = ____lualib.Map
4
4
  local __TS__New = ____lualib.__TS__New
5
5
  local Set = ____lualib.Set
6
- local __TS__Spread = ____lualib.__TS__Spread
7
- local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
8
6
  local ____exports = {}
9
- local copiedObjectIsTable, copiedObjectHasKeyAndValueString, copiedTableHasKeyAndValueNumber, copiedTableDoesNotCoerceTypes, copiedObjectHasNoReferencesForPrimitivesForward, copiedObjectHasNoReferencesForPrimitivesBackward, copiedObjectHasNoReferencesForArray, copiedObjectHasChildObject, copiedMapIsMap, copiedMapHasValue, copiedSetIsSet, copiedSetHasValue, copiedMapHasChildMap, copiedDefaultMapHasChildDefaultMap, copiedDefaultMapHasBrand, copiedSerializedMapHasStringKeyType, copiedSerializedMapHasNumberKeyType, copiedSerializedDefaultMapHasStringKeyType, copiedSerializedDefaultMapHasNumberKeyType
7
+ local copiedObjectIsTable, copiedObjectHasKeyAndValueString, copiedTableHasKeyAndValueNumber, copiedTableDoesNotCoerceTypes, copiedObjectHasNoReferencesForPrimitivesForward, copiedObjectHasNoReferencesForPrimitivesBackward, copiedObjectHasNoReferencesForArray, copiedObjectHasChildObject, copiedMapIsMap, copiedMapHasValue, copiedSetIsSet, copiedSetHasValue, copiedMapHasChildMap, copiedDefaultMapHasChildDefaultMap, copiedDefaultMapHasBrand, copiedSerializedMapHasStringKey, copiedSerializedMapHasNumberKey, copiedSerializedDefaultMapHasStringKey, copiedSerializedDefaultMapHasNumberKey
10
8
  local ____DefaultMap = require("classes.DefaultMap")
11
9
  local DefaultMap = ____DefaultMap.DefaultMap
12
10
  local ____SerializationBrand = require("enums.private.SerializationBrand")
@@ -299,62 +297,54 @@ function copiedDefaultMapHasBrand(self)
299
297
  error("The copied DefaultMap does not have the brand: " .. SerializationBrand.DEFAULT_MAP)
300
298
  end
301
299
  end
302
- function copiedSerializedMapHasStringKeyType(self)
300
+ function copiedSerializedMapHasStringKey(self)
303
301
  local mapKey = "123"
304
302
  local mapValue = 456
305
303
  local oldMap = __TS__New(Map)
306
304
  oldMap:set(mapKey, mapValue)
307
- local tempTable = deepCopy(nil, oldMap, SerializationType.SERIALIZE, "copiedSerializedMapHasStringKeyTypeSerialize")
308
- local newTable = deepCopy(nil, tempTable, SerializationType.DESERIALIZE, "copiedSerializedMapHasStringKeyTypeDeserialize")
305
+ local serializedOldMap = deepCopy(nil, oldMap, SerializationType.SERIALIZE, "copiedSerializedMapHasStringKey-serialize")
306
+ local newTable = deepCopy(nil, serializedOldMap, SerializationType.DESERIALIZE, "copiedSerializedMapHasStringKey-deserialize")
309
307
  local newMap = newTable
310
- if not __TS__ArrayIncludes(
311
- {__TS__Spread(newMap:keys())},
312
- mapKey
313
- ) then
314
- error((("The copied Map did not have a key of: " .. mapKey) .. " with type ") .. type(mapKey))
308
+ if not newMap:has(mapKey) then
309
+ local keyType = type(mapKey)
310
+ error((("The copied Map did not have a key of: " .. mapKey) .. " with type ") .. keyType)
315
311
  end
316
312
  end
317
- function copiedSerializedMapHasNumberKeyType(self)
313
+ function copiedSerializedMapHasNumberKey(self)
318
314
  local mapKey = 123
319
315
  local mapValue = 456
320
316
  local oldMap = __TS__New(Map)
321
317
  oldMap:set(mapKey, mapValue)
322
- local tempTable = deepCopy(nil, oldMap, SerializationType.SERIALIZE, "copiedSerializedMapHasNumberKeyTypeSerialize")
323
- local newTable = deepCopy(nil, tempTable, SerializationType.DESERIALIZE, "copiedSerializedMapHasNumberKeyTypeDeserialize")
318
+ local serializedOldMap = deepCopy(nil, oldMap, SerializationType.SERIALIZE, "copiedSerializedMapHasNumberKey-serialize")
319
+ local newTable = deepCopy(nil, serializedOldMap, SerializationType.DESERIALIZE, "copiedSerializedMapHasNumberKey-deserialize")
324
320
  local newMap = newTable
325
- if not __TS__ArrayIncludes(
326
- {__TS__Spread(newMap:keys())},
327
- mapKey
328
- ) then
329
- error((("The copied Map did not have a key of: " .. tostring(mapKey)) .. " with type ") .. type(mapKey))
321
+ if not newMap:has(mapKey) then
322
+ local keyType = type(mapKey)
323
+ error((("The copied Map did not have a key of: " .. tostring(mapKey)) .. " with type ") .. keyType)
330
324
  end
331
325
  end
332
- function copiedSerializedDefaultMapHasStringKeyType(self)
326
+ function copiedSerializedDefaultMapHasStringKey(self)
333
327
  local mapKey = "123"
334
- local oldMap = __TS__New(DefaultMap, 456)
335
- oldMap:getAndSetDefault(mapKey)
336
- local tempTable = deepCopy(nil, oldMap, SerializationType.SERIALIZE, "copiedSerializedDefaultMapHasStringKeyTypeSerialize")
337
- local newTable = deepCopy(nil, tempTable, SerializationType.DESERIALIZE, "copiedSerializedDefaultMapHasStringKeyTypeDeserialize")
338
- local newMap = newTable
339
- if not __TS__ArrayIncludes(
340
- {__TS__Spread(newMap:keys())},
341
- mapKey
342
- ) then
343
- error((("The copied DefaultMap did not have a key of: " .. mapKey) .. " with type ") .. type(mapKey))
328
+ local oldDefaultMap = __TS__New(DefaultMap, 456)
329
+ oldDefaultMap:getAndSetDefault(mapKey)
330
+ local serializedOldDefaultMap = deepCopy(nil, oldDefaultMap, SerializationType.SERIALIZE, "copiedSerializedDefaultMapHasStringKey-serialize")
331
+ local newTable = deepCopy(nil, serializedOldDefaultMap, SerializationType.DESERIALIZE, "copiedSerializedDefaultMapHasStringKey-deserialize")
332
+ local newDefaultMap = newTable
333
+ if not newDefaultMap:has(mapKey) then
334
+ local keyType = type(mapKey)
335
+ error((("The copied DefaultMap did not have a key of \"" .. mapKey) .. "\" with type: ") .. keyType)
344
336
  end
345
337
  end
346
- function copiedSerializedDefaultMapHasNumberKeyType(self)
338
+ function copiedSerializedDefaultMapHasNumberKey(self)
347
339
  local mapKey = 123
348
- local oldMap = __TS__New(DefaultMap, 456)
349
- oldMap:getAndSetDefault(mapKey)
350
- local tempTable = deepCopy(nil, oldMap, SerializationType.SERIALIZE, "copiedSerializedDefaultMapHasNumberKeyTypeSerialize")
351
- local newTable = deepCopy(nil, tempTable, SerializationType.DESERIALIZE, "copiedSerializedDefaultMapHasNumberKeyTypeDeserialize")
352
- local newMap = newTable
353
- if not __TS__ArrayIncludes(
354
- {__TS__Spread(newMap:keys())},
355
- mapKey
356
- ) then
357
- error((("The copied DefaultMap did not have a key of: " .. tostring(mapKey)) .. " with type ") .. type(mapKey))
340
+ local oldDefaultMap = __TS__New(DefaultMap, 456)
341
+ oldDefaultMap:getAndSetDefault(mapKey)
342
+ local serializedOldDefaultMap = deepCopy(nil, oldDefaultMap, SerializationType.SERIALIZE, "copiedSerializedDefaultMapHasNumberKey-serialize")
343
+ local newTable = deepCopy(nil, serializedOldDefaultMap, SerializationType.DESERIALIZE, "copiedSerializedDefaultMapHasNumberKey-deserialize")
344
+ local newDefaultMap = newTable
345
+ if not newDefaultMap:has(mapKey) then
346
+ local keyType = type(mapKey)
347
+ error((("The copied DefaultMap did not have a key of: " .. tostring(mapKey)) .. " with type ") .. keyType)
358
348
  end
359
349
  end
360
350
  --- Run the suite of tests that prove that the "deepCopy" helper function works properly.
@@ -376,10 +366,10 @@ function ____exports.runDeepCopyTests(self)
376
366
  copiedMapHasChildMap(nil)
377
367
  copiedDefaultMapHasChildDefaultMap(nil)
378
368
  copiedDefaultMapHasBrand(nil)
379
- copiedSerializedMapHasStringKeyType(nil)
380
- copiedSerializedMapHasNumberKeyType(nil)
381
- copiedSerializedDefaultMapHasStringKeyType(nil)
382
- copiedSerializedDefaultMapHasNumberKeyType(nil)
369
+ copiedSerializedMapHasStringKey(nil)
370
+ copiedSerializedMapHasNumberKey(nil)
371
+ copiedSerializedDefaultMapHasStringKey(nil)
372
+ copiedSerializedDefaultMapHasNumberKey(nil)
383
373
  log("All deep copy tests passed!")
384
374
  end
385
375
  return ____exports
@@ -6,6 +6,9 @@
6
6
  * fails, it will return a blank Lua table instead of throwing an error. (This allows execution to
7
7
  * continue in cases where users have no current save data or have manually removed their existing
8
8
  * save data.)
9
+ *
10
+ * Under the hood, this uses a custom JSON parser that was measured to be 11.8 times faster than the
11
+ * vanilla JSON parser.
9
12
  */
10
13
  export declare function jsonDecode(jsonString: string): LuaMap<AnyNotNil, unknown>;
11
14
  /**
@@ -14,6 +17,9 @@ export declare function jsonDecode(jsonString: string): LuaMap<AnyNotNil, unknow
14
17
  * In most cases, this function will be used for writing data to a "save#.dat" file. If encoding
15
18
  * fails, it will throw an error to prevent writing a blank string or corrupted data to a user's
16
19
  * "save#.dat" file.
20
+ *
21
+ * Under the hood, this uses a custom JSON parser that was measured to be 11.8 times faster than the
22
+ * vanilla JSON parser.
17
23
  */
18
24
  export declare function jsonEncode(luaTable: unknown): string;
19
25
  //# sourceMappingURL=jsonHelpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"jsonHelpers.d.ts","sourceRoot":"","sources":["../../src/functions/jsonHelpers.ts"],"names":[],"mappings":";AAWA;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CASzE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAOpD"}
1
+ {"version":3,"file":"jsonHelpers.d.ts","sourceRoot":"","sources":["../../src/functions/jsonHelpers.ts"],"names":[],"mappings":";AAWA;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CASzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAOpD"}
@@ -1,12 +1,12 @@
1
1
  local ____exports = {}
2
- local json = require("@NoResolution:json")
2
+ local jsonLua = require("lib.jsonLua")
3
3
  local ____log = require("functions.log")
4
4
  local logError = ____log.logError
5
5
  local function tryDecode(jsonString)
6
- return json.decode(jsonString)
6
+ return jsonLua.decode(jsonString)
7
7
  end
8
8
  local function tryEncode(luaTable)
9
- return json.encode(luaTable)
9
+ return jsonLua.encode(luaTable)
10
10
  end
11
11
  --- Converts a JSON string to a Lua table.
12
12
  --
@@ -14,6 +14,9 @@ end
14
14
  -- fails, it will return a blank Lua table instead of throwing an error. (This allows execution to
15
15
  -- continue in cases where users have no current save data or have manually removed their existing
16
16
  -- save data.)
17
+ --
18
+ -- Under the hood, this uses a custom JSON parser that was measured to be 11.8 times faster than the
19
+ -- vanilla JSON parser.
17
20
  function ____exports.jsonDecode(self, jsonString)
18
21
  local ok, luaTableOrErrMsg = pcall(tryDecode, jsonString)
19
22
  if not ok then
@@ -27,6 +30,9 @@ end
27
30
  -- In most cases, this function will be used for writing data to a "save#.dat" file. If encoding
28
31
  -- fails, it will throw an error to prevent writing a blank string or corrupted data to a user's
29
32
  -- "save#.dat" file.
33
+ --
34
+ -- Under the hood, this uses a custom JSON parser that was measured to be 11.8 times faster than the
35
+ -- vanilla JSON parser.
30
36
  function ____exports.jsonEncode(self, luaTable)
31
37
  local ok, jsonStringOrErrMsg = pcall(tryEncode, luaTable)
32
38
  if not ok then
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isaacscript-common",
3
- "version": "6.10.2",
3
+ "version": "6.11.0",
4
4
  "description": "Helper functions and features for IsaacScript mods.",
5
5
  "keywords": [
6
6
  "isaac",
@@ -32,11 +32,11 @@ export function runDeepCopyTests(): void {
32
32
  copiedDefaultMapHasChildDefaultMap();
33
33
  copiedDefaultMapHasBrand();
34
34
 
35
- copiedSerializedMapHasStringKeyType();
36
- copiedSerializedMapHasNumberKeyType();
35
+ copiedSerializedMapHasStringKey();
36
+ copiedSerializedMapHasNumberKey();
37
37
 
38
- copiedSerializedDefaultMapHasStringKeyType();
39
- copiedSerializedDefaultMapHasNumberKeyType();
38
+ copiedSerializedDefaultMapHasStringKey();
39
+ copiedSerializedDefaultMapHasNumberKey();
40
40
 
41
41
  log("All deep copy tests passed!");
42
42
  }
@@ -464,112 +464,108 @@ function copiedDefaultMapHasBrand() {
464
464
  }
465
465
  }
466
466
 
467
- function copiedSerializedMapHasStringKeyType() {
467
+ function copiedSerializedMapHasStringKey() {
468
468
  const mapKey = "123";
469
469
  const mapValue = 456;
470
470
  const oldMap = new Map<string, number>();
471
471
  oldMap.set(mapKey, mapValue);
472
472
 
473
- const tempTable = deepCopy(
473
+ const serializedOldMap = deepCopy(
474
474
  oldMap,
475
475
  SerializationType.SERIALIZE,
476
- "copiedSerializedMapHasStringKeyTypeSerialize",
476
+ "copiedSerializedMapHasStringKey-serialize",
477
477
  );
478
478
 
479
479
  const newTable = deepCopy(
480
- tempTable,
480
+ serializedOldMap,
481
481
  SerializationType.DESERIALIZE,
482
- "copiedSerializedMapHasStringKeyTypeDeserialize",
482
+ "copiedSerializedMapHasStringKey-deserialize",
483
483
  );
484
484
 
485
485
  const newMap = newTable as Map<string, number>;
486
- if (![...newMap.keys()].includes(mapKey)) {
486
+ if (!newMap.has(mapKey)) {
487
+ const keyType = type(mapKey);
487
488
  error(
488
- `The copied Map did not have a key of: ${mapKey} with type ${type(
489
- mapKey,
490
- )}`,
489
+ `The copied Map did not have a key of: ${mapKey} with type ${keyType}`,
491
490
  );
492
491
  }
493
492
  }
494
493
 
495
- function copiedSerializedMapHasNumberKeyType() {
494
+ function copiedSerializedMapHasNumberKey() {
496
495
  const mapKey = 123;
497
496
  const mapValue = 456;
498
497
  const oldMap = new Map<number, number>();
499
498
  oldMap.set(mapKey, mapValue);
500
499
 
501
- const tempTable = deepCopy(
500
+ const serializedOldMap = deepCopy(
502
501
  oldMap,
503
502
  SerializationType.SERIALIZE,
504
- "copiedSerializedMapHasNumberKeyTypeSerialize",
503
+ "copiedSerializedMapHasNumberKey-serialize",
505
504
  );
506
505
 
507
506
  const newTable = deepCopy(
508
- tempTable,
507
+ serializedOldMap,
509
508
  SerializationType.DESERIALIZE,
510
- "copiedSerializedMapHasNumberKeyTypeDeserialize",
509
+ "copiedSerializedMapHasNumberKey-deserialize",
511
510
  );
512
511
 
513
512
  const newMap = newTable as Map<number, number>;
514
- if (![...newMap.keys()].includes(mapKey)) {
513
+ if (!newMap.has(mapKey)) {
514
+ const keyType = type(mapKey);
515
515
  error(
516
- `The copied Map did not have a key of: ${mapKey} with type ${type(
517
- mapKey,
518
- )}`,
516
+ `The copied Map did not have a key of: ${mapKey} with type ${keyType}`,
519
517
  );
520
518
  }
521
519
  }
522
520
 
523
- function copiedSerializedDefaultMapHasStringKeyType() {
521
+ function copiedSerializedDefaultMapHasStringKey() {
524
522
  const mapKey = "123";
525
- const oldMap = new DefaultMap<string, number>(456);
526
- oldMap.getAndSetDefault(mapKey);
523
+ const oldDefaultMap = new DefaultMap<string, number>(456);
524
+ oldDefaultMap.getAndSetDefault(mapKey);
527
525
 
528
- const tempTable = deepCopy(
529
- oldMap,
526
+ const serializedOldDefaultMap = deepCopy(
527
+ oldDefaultMap,
530
528
  SerializationType.SERIALIZE,
531
- "copiedSerializedDefaultMapHasStringKeyTypeSerialize",
529
+ "copiedSerializedDefaultMapHasStringKey-serialize",
532
530
  );
533
531
 
534
532
  const newTable = deepCopy(
535
- tempTable,
533
+ serializedOldDefaultMap,
536
534
  SerializationType.DESERIALIZE,
537
- "copiedSerializedDefaultMapHasStringKeyTypeDeserialize",
535
+ "copiedSerializedDefaultMapHasStringKey-deserialize",
538
536
  );
539
537
 
540
- const newMap = newTable as DefaultMap<string, number>;
541
- if (![...newMap.keys()].includes(mapKey)) {
538
+ const newDefaultMap = newTable as DefaultMap<string, number>;
539
+ if (!newDefaultMap.has(mapKey)) {
540
+ const keyType = type(mapKey);
542
541
  error(
543
- `The copied DefaultMap did not have a key of: ${mapKey} with type ${type(
544
- mapKey,
545
- )}`,
542
+ `The copied DefaultMap did not have a key of "${mapKey}" with type: ${keyType}`,
546
543
  );
547
544
  }
548
545
  }
549
546
 
550
- function copiedSerializedDefaultMapHasNumberKeyType() {
547
+ function copiedSerializedDefaultMapHasNumberKey() {
551
548
  const mapKey = 123;
552
- const oldMap = new DefaultMap<number, number>(456);
553
- oldMap.getAndSetDefault(mapKey);
549
+ const oldDefaultMap = new DefaultMap<number, number>(456);
550
+ oldDefaultMap.getAndSetDefault(mapKey);
554
551
 
555
- const tempTable = deepCopy(
556
- oldMap,
552
+ const serializedOldDefaultMap = deepCopy(
553
+ oldDefaultMap,
557
554
  SerializationType.SERIALIZE,
558
- "copiedSerializedDefaultMapHasNumberKeyTypeSerialize",
555
+ "copiedSerializedDefaultMapHasNumberKey-serialize",
559
556
  );
560
557
 
561
558
  const newTable = deepCopy(
562
- tempTable,
559
+ serializedOldDefaultMap,
563
560
  SerializationType.DESERIALIZE,
564
- "copiedSerializedDefaultMapHasNumberKeyTypeDeserialize",
561
+ "copiedSerializedDefaultMapHasNumberKey-deserialize",
565
562
  );
566
563
 
567
- const newMap = newTable as DefaultMap<number, number>;
568
- if (![...newMap.keys()].includes(mapKey)) {
564
+ const newDefaultMap = newTable as DefaultMap<number, number>;
565
+ if (!newDefaultMap.has(mapKey)) {
566
+ const keyType = type(mapKey);
569
567
  error(
570
- `The copied DefaultMap did not have a key of: ${mapKey} with type ${type(
571
- mapKey,
572
- )}`,
568
+ `The copied DefaultMap did not have a key of: ${mapKey} with type ${keyType}`,
573
569
  );
574
570
  }
575
571
  }
@@ -1,12 +1,12 @@
1
- import * as json from "json";
1
+ import * as jsonLua from "../lib/jsonLua";
2
2
  import { logError } from "./log";
3
3
 
4
4
  function tryDecode(this: void, jsonString: string) {
5
- return json.decode(jsonString) as LuaMap;
5
+ return jsonLua.decode(jsonString) as LuaMap;
6
6
  }
7
7
 
8
8
  function tryEncode(this: void, luaTable: unknown) {
9
- return json.encode(luaTable);
9
+ return jsonLua.encode(luaTable);
10
10
  }
11
11
 
12
12
  /**
@@ -16,6 +16,9 @@ function tryEncode(this: void, luaTable: unknown) {
16
16
  * fails, it will return a blank Lua table instead of throwing an error. (This allows execution to
17
17
  * continue in cases where users have no current save data or have manually removed their existing
18
18
  * save data.)
19
+ *
20
+ * Under the hood, this uses a custom JSON parser that was measured to be 11.8 times faster than the
21
+ * vanilla JSON parser.
19
22
  */
20
23
  export function jsonDecode(jsonString: string): LuaMap<AnyNotNil, unknown> {
21
24
  const [ok, luaTableOrErrMsg] = pcall(tryDecode, jsonString);
@@ -34,6 +37,9 @@ export function jsonDecode(jsonString: string): LuaMap<AnyNotNil, unknown> {
34
37
  * In most cases, this function will be used for writing data to a "save#.dat" file. If encoding
35
38
  * fails, it will throw an error to prevent writing a blank string or corrupted data to a user's
36
39
  * "save#.dat" file.
40
+ *
41
+ * Under the hood, this uses a custom JSON parser that was measured to be 11.8 times faster than the
42
+ * vanilla JSON parser.
37
43
  */
38
44
  export function jsonEncode(luaTable: unknown): string {
39
45
  const [ok, jsonStringOrErrMsg] = pcall(tryEncode, luaTable);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * This is the custom JSON parser library called "json.lua". It is located at:
3
+ * https://github.com/rxi/json.lua
4
+ *
5
+ * This parser was measured to be 11.8 times faster than the vanilla parser at decoding a sample
6
+ * "save1.dat" file.
7
+ */
8
+
9
+ export function encode(this: void, data: unknown): string;
10
+ export function decode(this: void, data: string): unknown;
@@ -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