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.
@@ -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