• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1-- Obtained from https://github.com/rxi/json.lua.
2
3local json = { _version = "0.1.2" }
4
5-------------------------------------------------------------------------------
6-- Encode
7-------------------------------------------------------------------------------
8
9local encode
10
11local escape_char_map = {
12  [ "\\" ] = "\\",
13  [ "\"" ] = "\"",
14  [ "\b" ] = "b",
15  [ "\f" ] = "f",
16  [ "\n" ] = "n",
17  [ "\r" ] = "r",
18  [ "\t" ] = "t",
19}
20
21local escape_char_map_inv = { [ "/" ] = "/" }
22for k, v in pairs(escape_char_map) do
23  escape_char_map_inv[v] = k
24end
25
26
27local function escape_char(c)
28  return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
29end
30
31
32local function encode_nil(val)
33  return "null"
34end
35
36
37local function encode_table(val, stack)
38  local res = {}
39  stack = stack or {}
40
41  -- Circular reference?
42  if stack[val] then error("circular reference") end
43
44  stack[val] = true
45
46  if rawget(val, 1) ~= nil or next(val) == nil then
47    -- Treat as array -- check keys are valid and it is not sparse
48    local n = 0
49    for k in pairs(val) do
50      if type(k) ~= "number" then
51        error("invalid table: mixed or invalid key types")
52      end
53      n = n + 1
54    end
55    if n ~= #val then
56      error("invalid table: sparse array")
57    end
58    -- Encode
59    for i, v in ipairs(val) do
60      table.insert(res, encode(v, stack))
61    end
62    stack[val] = nil
63    return "[" .. table.concat(res, ",") .. "]"
64
65  else
66    -- Treat as an object
67    for k, v in pairs(val) do
68      if type(k) ~= "string" then
69        error("invalid table: mixed or invalid key types")
70      end
71      table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
72    end
73    stack[val] = nil
74    return "{" .. table.concat(res, ",") .. "}"
75  end
76end
77
78
79local function encode_string(val)
80  return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
81end
82
83
84local function encode_number(val)
85  -- Check for NaN, -inf and inf
86  if val ~= val or val <= -math.huge or val >= math.huge then
87    error("unexpected number value '" .. tostring(val) .. "'")
88  end
89  return string.format("%.14g", val)
90end
91
92
93local type_func_map = {
94  [ "nil"     ] = encode_nil,
95  [ "table"   ] = encode_table,
96  [ "string"  ] = encode_string,
97  [ "number"  ] = encode_number,
98  [ "boolean" ] = tostring,
99}
100
101
102encode = function(val, stack)
103  local t = type(val)
104  local f = type_func_map[t]
105  if f then
106    return f(val, stack)
107  end
108  error("unexpected type '" .. t .. "'")
109end
110
111
112function json.encode(val)
113  return ( encode(val) )
114end
115
116
117-------------------------------------------------------------------------------
118-- Decode
119-------------------------------------------------------------------------------
120
121local parse
122
123local function create_set(...)
124  local res = {}
125  for i = 1, select("#", ...) do
126    res[ select(i, ...) ] = true
127  end
128  return res
129end
130
131local space_chars   = create_set(" ", "\t", "\r", "\n")
132local delim_chars   = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
133local escape_chars  = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
134local literals      = create_set("true", "false", "null")
135
136local literal_map = {
137  [ "true"  ] = true,
138  [ "false" ] = false,
139  [ "null"  ] = nil,
140}
141
142
143local function next_char(str, idx, set, negate)
144  for i = idx, #str do
145    if set[str:sub(i, i)] ~= negate then
146      return i
147    end
148  end
149  return #str + 1
150end
151
152
153local function decode_error(str, idx, msg)
154  local line_count = 1
155  local col_count = 1
156  for i = 1, idx - 1 do
157    col_count = col_count + 1
158    if str:sub(i, i) == "\n" then
159      line_count = line_count + 1
160      col_count = 1
161    end
162  end
163  error( string.format("%s at line %d col %d", msg, line_count, col_count) )
164end
165
166
167local function codepoint_to_utf8(n)
168  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
169  local f = math.floor
170  if n <= 0x7f then
171    return string.char(n)
172  elseif n <= 0x7ff then
173    return string.char(f(n / 64) + 192, n % 64 + 128)
174  elseif n <= 0xffff then
175    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
176  elseif n <= 0x10ffff then
177    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
178                       f(n % 4096 / 64) + 128, n % 64 + 128)
179  end
180  error( string.format("invalid unicode codepoint '%x'", n) )
181end
182
183
184local function parse_unicode_escape(s)
185  local n1 = tonumber( s:sub(1, 4),  16 )
186  local n2 = tonumber( s:sub(7, 10), 16 )
187   -- Surrogate pair?
188  if n2 then
189    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
190  else
191    return codepoint_to_utf8(n1)
192  end
193end
194
195
196local function parse_string(str, i)
197  local res = ""
198  local j = i + 1
199  local k = j
200
201  while j <= #str do
202    local x = str:byte(j)
203
204    if x < 32 then
205      decode_error(str, j, "control character in string")
206
207    elseif x == 92 then -- `\`: Escape
208      res = res .. str:sub(k, j - 1)
209      j = j + 1
210      local c = str:sub(j, j)
211      if c == "u" then
212        local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
213                 or str:match("^%x%x%x%x", j + 1)
214                 or decode_error(str, j - 1, "invalid unicode escape in string")
215        res = res .. parse_unicode_escape(hex)
216        j = j + #hex
217      else
218        if not escape_chars[c] then
219          decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
220        end
221        res = res .. escape_char_map_inv[c]
222      end
223      k = j + 1
224
225    elseif x == 34 then -- `"`: End of string
226      res = res .. str:sub(k, j - 1)
227      return res, j + 1
228    end
229
230    j = j + 1
231  end
232
233  decode_error(str, i, "expected closing quote for string")
234end
235
236
237local function parse_number(str, i)
238  local x = next_char(str, i, delim_chars)
239  local s = str:sub(i, x - 1)
240  local n = tonumber(s)
241  if not n then
242    decode_error(str, i, "invalid number '" .. s .. "'")
243  end
244  return n, x
245end
246
247
248local function parse_literal(str, i)
249  local x = next_char(str, i, delim_chars)
250  local word = str:sub(i, x - 1)
251  if not literals[word] then
252    decode_error(str, i, "invalid literal '" .. word .. "'")
253  end
254  return literal_map[word], x
255end
256
257
258local function parse_array(str, i)
259  local res = {}
260  local n = 1
261  i = i + 1
262  while 1 do
263    local x
264    i = next_char(str, i, space_chars, true)
265    -- Empty / end of array?
266    if str:sub(i, i) == "]" then
267      i = i + 1
268      break
269    end
270    -- Read token
271    x, i = parse(str, i)
272    res[n] = x
273    n = n + 1
274    -- Next token
275    i = next_char(str, i, space_chars, true)
276    local chr = str:sub(i, i)
277    i = i + 1
278    if chr == "]" then break end
279    if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
280  end
281  return res, i
282end
283
284
285local function parse_object(str, i)
286  local res = {}
287  i = i + 1
288  while 1 do
289    local key, val
290    i = next_char(str, i, space_chars, true)
291    -- Empty / end of object?
292    if str:sub(i, i) == "}" then
293      i = i + 1
294      break
295    end
296    -- Read key
297    if str:sub(i, i) ~= '"' then
298      decode_error(str, i, "expected string for key")
299    end
300    key, i = parse(str, i)
301    -- Read ':' delimiter
302    i = next_char(str, i, space_chars, true)
303    if str:sub(i, i) ~= ":" then
304      decode_error(str, i, "expected ':' after key")
305    end
306    i = next_char(str, i + 1, space_chars, true)
307    -- Read value
308    val, i = parse(str, i)
309    -- Set
310    res[key] = val
311    -- Next token
312    i = next_char(str, i, space_chars, true)
313    local chr = str:sub(i, i)
314    i = i + 1
315    if chr == "}" then break end
316    if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
317  end
318  return res, i
319end
320
321
322local char_func_map = {
323  [ '"' ] = parse_string,
324  [ "0" ] = parse_number,
325  [ "1" ] = parse_number,
326  [ "2" ] = parse_number,
327  [ "3" ] = parse_number,
328  [ "4" ] = parse_number,
329  [ "5" ] = parse_number,
330  [ "6" ] = parse_number,
331  [ "7" ] = parse_number,
332  [ "8" ] = parse_number,
333  [ "9" ] = parse_number,
334  [ "-" ] = parse_number,
335  [ "t" ] = parse_literal,
336  [ "f" ] = parse_literal,
337  [ "n" ] = parse_literal,
338  [ "[" ] = parse_array,
339  [ "{" ] = parse_object,
340}
341
342
343parse = function(str, idx)
344  local chr = str:sub(idx, idx)
345  local f = char_func_map[chr]
346  if f then
347    return f(str, idx)
348  end
349  decode_error(str, idx, "unexpected character '" .. chr .. "'")
350end
351
352
353function json.decode(str)
354  if type(str) ~= "string" then
355    error("expected argument of type string, got " .. type(str))
356  end
357  local res, idx = parse(str, next_char(str, 1, space_chars, true))
358  idx = next_char(str, idx, space_chars, true)
359  if idx <= #str then
360    decode_error(str, idx, "trailing garbage")
361  end
362  return res
363end
364
365
366return json