|
diff --git a/stdlib/json.lua b/stdlib/json.lua
|
|
|
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 |