2 -- https://github.com/rxi/json.lua
4 -- Copyright (c) 2020 rxi
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:
13 -- The above copyright notice and this permission notice shall be included in all
14 -- copies or substantial portions of the Software.
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
25 local json
= { _version
= "0.1.2" }
27 -------------------------------------------------------------------------------
29 -------------------------------------------------------------------------------
33 local escape_char_map
= {
43 local escape_char_map_inv
= { [ "/" ] = "/" }
44 for k
, v
in pairs(escape_char_map
) do
45 escape_char_map_inv
[v
] = k
49 local function escape_char(c
)
50 return "\\" .. (escape_char_map
[c
] or string.format("u%04x", c
:byte()))
54 local function encode_nil(val
)
59 local function encode_table(val
, stack
)
63 -- Circular reference?
64 if stack
[val
] then error("circular reference") end
68 if rawget(val
, 1) ~= nil or next(val
) == nil then
69 -- Treat as array -- check keys are valid and it is not sparse
71 for k
in pairs(val
) do
72 if type(k
) ~= "number" then
73 error("invalid table: mixed or invalid key types")
78 error("invalid table: sparse array")
81 for i
, v
in ipairs(val
) do
82 table.insert(res
, encode(v
, stack
))
85 return "[" .. table.concat(res
, ",") .. "]"
89 for k
, v
in pairs(val
) do
90 if type(k
) ~= "string" then
91 error("invalid table: mixed or invalid key types")
93 table.insert(res
, encode(k
, stack
) .. ":" .. encode(v
, stack
))
96 return "{" .. table.concat(res
, ",") .. "}"
101 local function encode_string(val
)
102 return '"' .. val
:gsub('[%z\1-\31\\"]', escape_char
) .. '"'
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
) .. "'")
111 return string.format("%.14g", val
)
115 local type_func_map
= {
116 [ "nil" ] = encode_nil
,
117 [ "table" ] = encode_table
,
118 [ "string" ] = encode_string
,
119 [ "number" ] = encode_number
,
120 [ "boolean" ] = tostring,
124 encode
= function(val
, stack
)
126 local f
= type_func_map
[t
]
130 error("unexpected type '" .. t
.. "'")
134 function json
.encode(val
)
135 return ( encode(val
) )
139 -------------------------------------------------------------------------------
141 -------------------------------------------------------------------------------
145 local function create_set(...)
147 for i
= 1, select("#", ...) do
148 res
[ select(i
, ...) ] = true
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")
158 local literal_map
= {
165 local function next_char(str
, idx
, set
, negate
)
167 if set
[str
:sub(i
, i
)] ~= negate
then
175 local function decode_error(str
, idx
, msg
)
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
185 error( string.format("%s at line %d col %d", msg
, line_count
, col_count
) )
189 local function codepoint_to_utf8(n
)
190 -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
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)
202 error( string.format("invalid unicode codepoint '%x'", n
) )
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 )
211 return codepoint_to_utf8((n1
- 0xd800) * 0x400 + (n2
- 0xdc00) + 0x10000)
213 return codepoint_to_utf8(n1
)
218 local function parse_string(str
, i
)
224 local x
= str
:byte(j
)
227 decode_error(str
, j
, "control character in string")
229 elseif x
== 92 then -- `\`: Escape
230 res
= res
.. str
:sub(k
, j
- 1)
232 local c
= str
:sub(j
, j
)
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
)
240 if not escape_chars
[c
] then
241 decode_error(str
, j
- 1, "invalid escape char '" .. c
.. "' in string")
243 res
= res
.. escape_char_map_inv
[c
]
247 elseif x
== 34 then -- `"`: End of string
248 res
= res
.. str
:sub(k
, j
- 1)
255 decode_error(str
, i
, "expected closing quote for string")
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
)
264 decode_error(str
, i
, "invalid number '" .. s
.. "'")
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
.. "'")
276 return literal_map
[word
], x
280 local function parse_array(str
, i
)
286 i
= next_char(str
, i
, space_chars
, true)
287 -- Empty / end of array?
288 if str
:sub(i
, i
) == "]" then
297 i
= next_char(str
, i
, space_chars
, true)
298 local chr
= str
:sub(i
, i
)
300 if chr
== "]" then break end
301 if chr
~= "," then decode_error(str
, i
, "expected ']' or ','") end
307 local function parse_object(str
, i
)
312 i
= next_char(str
, i
, space_chars
, true)
313 -- Empty / end of object?
314 if str
:sub(i
, i
) == "}" then
319 if str
:sub(i
, i
) ~= '"' then
320 decode_error(str
, i
, "expected string for key")
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")
328 i
= next_char(str
, i
+ 1, space_chars
, true)
330 val
, i
= parse(str
, i
)
334 i
= next_char(str
, i
, space_chars
, true)
335 local chr
= str
:sub(i
, i
)
337 if chr
== "}" then break end
338 if chr
~= "," then decode_error(str
, i
, "expected '}' or ','") end
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
,
365 parse
= function(str
, idx
)
366 local chr
= str
:sub(idx
, idx
)
367 local f
= char_func_map
[chr
]
371 decode_error(str
, idx
, "unexpected character '" .. chr
.. "'")
375 function json
.decode(str
)
376 if type(str
) ~= "string" then
377 error("expected argument of type string, got " .. type(str
))
379 local res
, idx
= parse(str
, next_char(str
, 1, space_chars
, true))
380 idx
= next_char(str
, idx
, space_chars
, true)
382 decode_error(str
, idx
, "trailing garbage")