4 -- Copyright (c) 2015 rxi
6 -- This library is free software; you can redistribute it and/or modify it
7 -- under the terms of the MIT license. See LICENSE for details.
10 Json
= { _version
= "0.1.0" }
12 -------------------------------------------------------------------------------
14 -------------------------------------------------------------------------------
18 local escape_char_map
= {
28 local escape_char_map_inv
= { [ "\\/" ] = "/" }
29 for k
, v
in pairs(escape_char_map
) do
30 escape_char_map_inv
[v
] = k
34 local function escape_char(c
)
35 return escape_char_map
[c
] or string.format("\\u%04x", c
:byte())
39 local function encode_nil(val
)
44 local function encode_table(val
, stack
)
48 -- Circular reference?
49 if stack
[val
] then error("circular reference") end
53 if val
[1] ~= nil or next(val
) == nil then
54 -- Treat as array -- check keys are valid and it is not sparse
56 for k
in pairs(val
) do
57 if type(k
) ~= "number" then
58 error("invalid table: mixed or invalid key types")
63 error("invalid table: sparse array")
66 for i
, v
in ipairs(val
) do
67 table.insert(res
, encode(v
, stack
))
70 return "[" .. table.concat(res
, ",") .. "]"
74 for k
, v
in pairs(val
) do
75 if type(k
) ~= "string" then
76 error("invalid table: mixed or invalid key types")
78 table.insert(res
, encode(k
, stack
) .. ":" .. encode(v
, stack
))
81 return "{" .. table.concat(res
, ",") .. "}"
86 local function encode_string(val
)
87 return '"' .. val
:gsub('[%z\1-\31\\"]', escape_char
) .. '"'
91 local function encode_number(val
)
92 -- Check for NaN, -inf and inf
93 if val
~= val
or val
<= -math
.huge
or val
>= math
.huge
then
94 error("unexpected number value '" .. tostring(val
) .. "'")
96 return string.format("%.3f", val
)
99 local type_func_map
= {
100 [ "nil" ] = encode_nil
,
101 [ "table" ] = encode_table
,
102 [ "string" ] = encode_string
,
103 [ "number" ] = encode_number
,
104 [ "boolean" ] = tostring,
108 encode
= function(val
, stack
)
110 local f
= type_func_map
[t
]
114 error("unexpected type '" .. t
.. "'")
118 function Json
.encode(val
)
119 return ( encode(val
) )
123 -------------------------------------------------------------------------------
125 -------------------------------------------------------------------------------
129 local function create_set(...)
131 for i
= 1, select("#", ...) do
132 res
[ select(i
, ...) ] = true
137 local space_chars
= create_set(" ", "\t", "\r", "\n")
138 local delim_chars
= create_set(" ", "\t", "\r", "\n", "]", "}", ",")
139 local escape_chars
= create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
140 local literals
= create_set("true", "false", "null")
142 local literal_map
= {
149 local function next_char(str
, idx
, set
, negate
)
151 if set
[str
:sub(i
, i
)] ~= negate
then
159 local function decode_error(str
, idx
, msg
)
162 for i
= 1, idx
- 1 do
163 col_count
= col_count
+ 1
164 if str
:sub(i
, i
) == "\n" then
165 line_count
= line_count
+ 1
169 error( string.format("%s at line %d col %d", msg
, line_count
, col_count
) )
173 local function codepoint_to_utf8(n
)
174 -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
177 return string.char(n
)
178 elseif n
<= 0x7ff then
179 return string.char(f(n
/ 64) + 192, n
% 64 + 128)
180 elseif n
<= 0xffff then
181 return string.char(f(n
/ 4096) + 224, f(n
% 4096 / 64) + 128, n
% 64 + 128)
182 elseif n
<= 0x10ffff then
183 return string.char(f(n
/ 262144) + 240, f(n
% 262144 / 4096) + 128,
184 f(n
% 4096 / 64) + 128, n
% 64 + 128)
186 error( string.format("invalid unicode codepoint '%x'", n
) )
190 local function parse_unicode_escape(s
)
191 local n1
= tonumber( s
:sub(3, 6), 16 )
192 local n2
= tonumber( s
:sub(9, 12), 16 )
195 return codepoint_to_utf8((n1
- 0xd800) * 0x400 + (n2
- 0xdc00) + 0x10000)
197 return codepoint_to_utf8(n1
)
202 local function parse_string(str
, i
)
203 local has_unicode_escape
= false
204 local has_surrogate_escape
= false
205 local has_escape
= false
207 for j
= i
+ 1, #str
do
208 local x
= str
:byte(j
)
211 decode_error(str
, j
, "control character in string")
214 if last
== 92 then -- "\\" (escape char)
215 if x
== 117 then -- "u" (unicode escape sequence)
216 local hex
= str
:sub(j
+ 1, j
+ 5)
217 if not hex
:find("%x%x%x%x") then
218 decode_error(str
, j
, "invalid unicode escape in string")
220 if hex
:find("^[dD][89aAbB]") then
221 has_surrogate_escape
= true
223 has_unicode_escape
= true
226 local c
= string.char(x
)
227 if not escape_chars
[c
] then
228 decode_error(str
, j
, "invalid escape char '" .. c
.. "' in string")
234 elseif x
== 34 then -- '"' (end of string)
235 local s
= str
:sub(i
+ 1, j
- 1)
236 if has_surrogate_escape
then
237 s
= s
:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape
)
239 if has_unicode_escape
then
240 s
= s
:gsub("\\u....", parse_unicode_escape
)
243 s
= s
:gsub("\\.", escape_char_map_inv
)
251 decode_error(str
, i
, "expected closing quote for string")
255 local function parse_number(str
, i
)
256 local x
= next_char(str
, i
, delim_chars
)
257 local s
= str
:sub(i
, x
- 1)
258 local n
= tonumber(s
)
260 decode_error(str
, i
, "invalid number '" .. s
.. "'")
266 local function parse_literal(str
, i
)
267 local x
= next_char(str
, i
, delim_chars
)
268 local word
= str
:sub(i
, x
- 1)
269 if not literals
[word
] then
270 decode_error(str
, i
, "invalid literal '" .. word
.. "'")
272 return literal_map
[word
], x
276 local function parse_array(str
, i
)
282 i
= next_char(str
, i
, space_chars
, true)
283 -- Empty / end of array?
284 if str
:sub(i
, i
) == "]" then
293 i
= next_char(str
, i
, space_chars
, true)
294 local chr
= str
:sub(i
, i
)
296 if chr
== "]" then break end
297 if chr
~= "," then decode_error(str
, i
, "expected ']' or ','") end
303 local function parse_object(str
, i
)
308 i
= next_char(str
, i
, space_chars
, true)
309 -- Empty / end of object?
310 if str
:sub(i
, i
) == "}" then
315 if str
:sub(i
, i
) ~= '"' then
316 decode_error(str
, i
, "expected string for key")
318 key
, i
= parse(str
, i
)
319 -- Read ':' delimiter
320 i
= next_char(str
, i
, space_chars
, true)
321 if str
:sub(i
, i
) ~= ":" then
322 decode_error(str
, i
, "expected ':' after key")
324 i
= next_char(str
, i
+ 1, space_chars
, true)
326 val
, i
= parse(str
, i
)
330 i
= next_char(str
, i
, space_chars
, true)
331 local chr
= str
:sub(i
, i
)
333 if chr
== "}" then break end
334 if chr
~= "," then decode_error(str
, i
, "expected '}' or ','") end
340 local char_func_map
= {
341 [ '"' ] = parse_string
,
342 [ "0" ] = parse_number
,
343 [ "1" ] = parse_number
,
344 [ "2" ] = parse_number
,
345 [ "3" ] = parse_number
,
346 [ "4" ] = parse_number
,
347 [ "5" ] = parse_number
,
348 [ "6" ] = parse_number
,
349 [ "7" ] = parse_number
,
350 [ "8" ] = parse_number
,
351 [ "9" ] = parse_number
,
352 [ "-" ] = parse_number
,
353 [ "t" ] = parse_literal
,
354 [ "f" ] = parse_literal
,
355 [ "n" ] = parse_literal
,
356 [ "[" ] = parse_array
,
357 [ "{" ] = parse_object
,
361 parse
= function(str
, idx
)
362 local chr
= str
:sub(idx
, idx
)
363 local f
= char_func_map
[chr
]
367 decode_error(str
, idx
, "unexpected character '" .. chr
.. "'")
371 function Json
.decode(str
)
372 if type(str
) ~= "string" then
373 error("expected argument of type string, got " .. type(str
))
375 return ( parse(str
, next_char(str
, 1, space_chars
, true)) )
379 RYZOM_JSON_VERSION
= 324