Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

843
LINES

< > BotCompany Repo | #44 // dkjson

Source file

1  
    -- Module options:
2  
    local always_try_using_lpeg = true
3  
    local register_global_module_table = false
4  
    local global_module_name = 'json'
5  
6  
    --[==[
7  
8  
David Kolf's JSON module for Lua 5.1/5.2
9  
========================================
10  
11  
*Version 2.4*
12  
13  
In the default configuration this module writes no global values, not even
14  
the module table. Import it using
15  
16  
    json = require ("dkjson")
17  
18  
In environments where `require` or a similiar function are not available
19  
and you cannot receive the return value of the module, you can set the
20  
option `register_global_module_table` to `true`.  The module table will
21  
then be saved in the global variable with the name given by the option
22  
`global_module_name`.
23  
24  
Exported functions and values:
25  
26  
`json.encode (object [, state])`
27  
--------------------------------
28  
29  
Create a string representing the object. `Object` can be a table,
30  
a string, a number, a boolean, `nil`, `json.null` or any object with
31  
a function `__tojson` in its metatable. A table can only use strings
32  
and numbers as keys and its values have to be valid objects as
33  
well. It raises an error for any invalid data types or reference
34  
cycles.
35  
36  
`state` is an optional table with the following fields:
37  
38  
  - `indent`  
39  
    When `indent` (a boolean) is set, the created string will contain
40  
    newlines and indentations. Otherwise it will be one long line.
41  
  - `keyorder`  
42  
    `keyorder` is an array to specify the ordering of keys in the
43  
    encoded output. If an object has keys which are not in this array
44  
    they are written after the sorted keys.
45  
  - `level`  
46  
    This is the initial level of indentation used when `indent` is
47  
    set. For each level two spaces are added. When absent it is set
48  
    to 0.
49  
  - `buffer`  
50  
    `buffer` is an array to store the strings for the result so they
51  
    can be concatenated at once. When it isn't given, the encode
52  
    function will create it temporary and will return the
53  
    concatenated result.
54  
  - `bufferlen`  
55  
    When `bufferlen` is set, it has to be the index of the last
56  
    element of `buffer`.
57  
  - `tables`  
58  
    `tables` is a set to detect reference cycles. It is created
59  
    temporary when absent. Every table that is currently processed
60  
    is used as key, the value is `true`.
61  
62  
When `state.buffer` was set, the return value will be `true` on
63  
success. Without `state.buffer` the return value will be a string.
64  
65  
`json.decode (string [, position [, null]])`
66  
--------------------------------------------
67  
68  
Decode `string` starting at `position` or at 1 if `position` was
69  
omitted.
70  
71  
`null` is an optional value to be returned for null values. The
72  
default is `nil`, but you could set it to `json.null` or any other
73  
value.
74  
75  
The return values are the object or `nil`, the position of the next
76  
character that doesn't belong to the object, and in case of errors
77  
an error message.
78  
79  
Two metatables are created. Every array or object that is decoded gets
80  
a metatable with the `__jsontype` field set to either `array` or
81  
`object`. If you want to provide your own metatables use the syntax
82  
83  
    json.decode (string, position, null, objectmeta, arraymeta)
84  
85  
To prevent the assigning of metatables pass `nil`:
86  
87  
    json.decode (string, position, null, nil)
88  
89  
`<metatable>.__jsonorder`
90  
-------------------------
91  
92  
`__jsonorder` can overwrite the `keyorder` for a specific table.
93  
94  
`<metatable>.__jsontype`
95  
------------------------
96  
97  
`__jsontype` can be either `"array"` or `"object"`. This value is only
98  
checked for empty tables. (The default for empty tables is `"array"`).
99  
100  
`<metatable>.__tojson (self, state)`
101  
------------------------------------
102  
103  
You can provide your own `__tojson` function in a metatable. In this
104  
function you can either add directly to the buffer and return true,
105  
or you can return a string. On errors nil and a message should be
106  
returned.
107  
108  
`json.null`
109  
-----------
110  
111  
You can use this value for setting explicit `null` values.
112  
113  
`json.version`
114  
--------------
115  
116  
Set to `"dkjson 2.4"`.
117  
118  
`json.quotestring (string)`
119  
---------------------------
120  
121  
Quote a UTF-8 string and escape critical characters using JSON
122  
escape sequences. This function is only necessary when you build
123  
your own `__tojson` functions.
124  
125  
`json.addnewline (state)`
126  
-------------------------
127  
128  
When `state.indent` is set, add a newline to `state.buffer` and spaces
129  
according to `state.level`.
130  
131  
LPeg support
132  
------------
133  
134  
When the local configuration variable `always_try_using_lpeg` is set,
135  
this module tries to load LPeg to replace the `decode` function. The
136  
speed increase is significant. You can get the LPeg module at
137  
  <http://www.inf.puc-rio.br/~roberto/lpeg/>.
138  
When LPeg couldn't be loaded, the pure Lua functions stay active.
139  
140  
In case you don't want this module to require LPeg on its own,
141  
disable the option `always_try_using_lpeg` in the options section at
142  
the top of the module.
143  
144  
In this case you can later load LPeg support using
145  
146  
### `json.use_lpeg ()`
147  
148  
Require the LPeg module and replace the functions `quotestring` and
149  
and `decode` with functions that use LPeg patterns.
150  
This function returns the module table, so you can load the module
151  
using:
152  
153  
    json = require "dkjson".use_lpeg()
154  
155  
Alternatively you can use `pcall` so the JSON module still works when
156  
LPeg isn't found.
157  
158  
    json = require "dkjson"
159  
    pcall (json.use_lpeg)
160  
161  
### `json.using_lpeg`
162  
163  
This variable is set to `true` when LPeg was loaded successfully.
164  
165  
---------------------------------------------------------------------
166  
167  
Contact
168  
-------
169  
170  
You can contact the author by sending an e-mail to 'david' at the
171  
domain 'dkolf.de'.
172  
173  
---------------------------------------------------------------------
174  
175  
*Copyright (C) 2010-2013 David Heiko Kolf*
176  
177  
Permission is hereby granted, free of charge, to any person obtaining
178  
a copy of this software and associated documentation files (the
179  
"Software"), to deal in the Software without restriction, including
180  
without limitation the rights to use, copy, modify, merge, publish,
181  
distribute, sublicense, and/or sell copies of the Software, and to
182  
permit persons to whom the Software is furnished to do so, subject to
183  
the following conditions:
184  
185  
The above copyright notice and this permission notice shall be
186  
included in all copies or substantial portions of the Software.
187  
188  
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
189  
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
190  
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
191  
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
192  
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
193  
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
194  
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
195  
SOFTWARE.
196  
197  
<!-- This documentation can be parsed using Markdown to generate HTML.
198  
     The source code is enclosed in a HTML comment so it won't be displayed
199  
     by browsers, but it should be removed from the final HTML file as
200  
     it isn't a valid HTML comment (and wastes space).
201  
  -->
202  
203  
<!--]==]
204  
205  
-- global dependencies:
206  
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
207  
      pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
208  
local error, require, pcall, select = error, require, pcall, select
209  
local floor, huge = math.floor, math.huge
210  
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
211  
      string.rep, string.gsub, string.sub, string.byte, string.char,
212  
      string.find, string.len, string.format
213  
local strmatch = string.match
214  
local concat = table.concat
215  
216  
local json = { version = "dkjson 2.4" }
217  
218  
if register_global_module_table then
219  
  _G[global_module_name] = json
220  
end
221  
222  
local _ENV = nil -- blocking globals in Lua 5.2
223  
224  
pcall (function()
225  
  -- Enable access to blocked metatables.
226  
  -- Don't worry, this module doesn't change anything in them.
227  
  local debmeta = require "debug".getmetatable
228  
  if debmeta then getmetatable = debmeta end
229  
end)
230  
231  
json.null = setmetatable ({}, {
232  
  __tojson = function () return "null" end
233  
})
234  
235  
local function isarray (tbl)
236  
  local max, n, arraylen = 0, 0, 0
237  
  for k,v in pairs (tbl) do
238  
    if k == 'n' and type(v) == 'number' then
239  
      arraylen = v
240  
      if v > max then
241  
        max = v
242  
      end
243  
    else
244  
      if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
245  
        return false
246  
      end
247  
      if k > max then
248  
        max = k
249  
      end
250  
      n = n + 1
251  
    end
252  
  end
253  
  if max > 10 and max > arraylen and max > n * 2 then
254  
    return false -- don't create an array with too many holes
255  
  end
256  
  return true, max
257  
end
258  
259  
local escapecodes = {
260  
  ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
261  
  ["\n"] = "\\n",  ["\r"] = "\\r",  ["\t"] = "\\t"
262  
}
263  
264  
local function escapeutf8 (uchar)
265  
  local value = escapecodes[uchar]
266  
  if value then
267  
    return value
268  
  end
269  
  local a, b, c, d = strbyte (uchar, 1, 4)
270  
  a, b, c, d = a or 0, b or 0, c or 0, d or 0
271  
  if a <= 0x7f then
272  
    value = a
273  
  elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
274  
    value = (a - 0xc0) * 0x40 + b - 0x80
275  
  elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
276  
    value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
277  
  elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
278  
    value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
279  
  else
280  
    return ""
281  
  end
282  
  if value <= 0xffff then
283  
    return strformat ("\\u%.4x", value)
284  
  elseif value <= 0x10ffff then
285  
    -- encode as UTF-16 surrogate pair
286  
    value = value - 0x10000
287  
    local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
288  
    return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
289  
  else
290  
    return ""
291  
  end
292  
end
293  
294  
local function fsub (str, pattern, repl)
295  
  -- gsub always builds a new string in a buffer, even when no match
296  
  -- exists. First using find should be more efficient when most strings
297  
  -- don't contain the pattern.
298  
  if strfind (str, pattern) then
299  
    return gsub (str, pattern, repl)
300  
  else
301  
    return str
302  
  end
303  
end
304  
305  
local function quotestring (value)
306  
  -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
307  
  value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
308  
  if strfind (value, "[\194\216\220\225\226\239]") then
309  
    value = fsub (value, "\194[\128-\159\173]", escapeutf8)
310  
    value = fsub (value, "\216[\128-\132]", escapeutf8)
311  
    value = fsub (value, "\220\143", escapeutf8)
312  
    value = fsub (value, "\225\158[\180\181]", escapeutf8)
313  
    value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
314  
    value = fsub (value, "\226\129[\160-\175]", escapeutf8)
315  
    value = fsub (value, "\239\187\191", escapeutf8)
316  
    value = fsub (value, "\239\191[\176-\191]", escapeutf8)
317  
  end
318  
  return "\"" .. value .. "\""
319  
end
320  
json.quotestring = quotestring
321  
322  
local function replace(str, o, n)
323  
  local i, j = strfind (str, o, 1, true)
324  
  if i then
325  
    return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
326  
  else
327  
    return str
328  
  end
329  
end
330  
331  
-- locale independent num2str and str2num functions
332  
local decpoint, numfilter
333  
334  
local function updatedecpoint ()
335  
  decpoint = strmatch(tostring(0.5), "([^05+])")
336  
  -- build a filter that can be used to remove group separators
337  
  numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
338  
end
339  
340  
updatedecpoint()
341  
342  
local function num2str (num)
343  
  return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
344  
end
345  
346  
local function str2num (str)
347  
  local num = tonumber(replace(str, ".", decpoint))
348  
  if not num then
349  
    updatedecpoint()
350  
    num = tonumber(replace(str, ".", decpoint))
351  
  end
352  
  return num
353  
end
354  
355  
local function addnewline2 (level, buffer, buflen)
356  
  buffer[buflen+1] = "\n"
357  
  buffer[buflen+2] = strrep ("  ", level)
358  
  buflen = buflen + 2
359  
  return buflen
360  
end
361  
362  
function json.addnewline (state)
363  
  if state.indent then
364  
    state.bufferlen = addnewline2 (state.level or 0,
365  
                           state.buffer, state.bufferlen or #(state.buffer))
366  
  end
367  
end
368  
369  
local encode2 -- forward declaration
370  
371  
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder)
372  
  local kt = type (key)
373  
  if kt ~= 'string' and kt ~= 'number' then
374  
    return nil, "type '" .. kt .. "' is not supported as a key by JSON."
375  
  end
376  
  if prev then
377  
    buflen = buflen + 1
378  
    buffer[buflen] = ","
379  
  end
380  
  if indent then
381  
    buflen = addnewline2 (level, buffer, buflen)
382  
  end
383  
  buffer[buflen+1] = quotestring (key)
384  
  buffer[buflen+2] = ":"
385  
  return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder)
386  
end
387  
388  
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
389  
  local valtype = type (value)
390  
  local valmeta = getmetatable (value)
391  
  valmeta = type (valmeta) == 'table' and valmeta -- only tables
392  
  local valtojson = valmeta and valmeta.__tojson
393  
  if valtojson then
394  
    if tables[value] then
395  
      return nil, "reference cycle"
396  
    end
397  
    tables[value] = true
398  
    local state = {
399  
        indent = indent, level = level, buffer = buffer,
400  
        bufferlen = buflen, tables = tables, keyorder = globalorder
401  
    }
402  
    local ret, msg = valtojson (value, state)
403  
    if not ret then return nil, msg end
404  
    tables[value] = nil
405  
    buflen = state.bufferlen
406  
    if type (ret) == 'string' then
407  
      buflen = buflen + 1
408  
      buffer[buflen] = ret
409  
    end
410  
  elseif value == nil then
411  
    buflen = buflen + 1
412  
    buffer[buflen] = "null"
413  
  elseif valtype == 'number' then
414  
    local s
415  
    if value ~= value or value >= huge or -value >= huge then
416  
      -- This is the behaviour of the original JSON implementation.
417  
      s = "null"
418  
    else
419  
      s = num2str (value)
420  
    end
421  
    buflen = buflen + 1
422  
    buffer[buflen] = s
423  
  elseif valtype == 'boolean' then
424  
    buflen = buflen + 1
425  
    buffer[buflen] = value and "true" or "false"
426  
  elseif valtype == 'string' then
427  
    buflen = buflen + 1
428  
    buffer[buflen] = quotestring (value)
429  
  elseif valtype == 'table' then
430  
    if tables[value] then
431  
      return nil, "reference cycle"
432  
    end
433  
    tables[value] = true
434  
    level = level + 1
435  
    local isa, n = isarray (value)
436  
    if n == 0 and valmeta and valmeta.__jsontype == 'object' then
437  
      isa = false
438  
    end
439  
    local msg
440  
    if isa then -- JSON array
441  
      buflen = buflen + 1
442  
      buffer[buflen] = "["
443  
      for i = 1, n do
444  
        buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder)
445  
        if not buflen then return nil, msg end
446  
        if i < n then
447  
          buflen = buflen + 1
448  
          buffer[buflen] = ","
449  
        end
450  
      end
451  
      buflen = buflen + 1
452  
      buffer[buflen] = "]"
453  
    else -- JSON object
454  
      local prev = false
455  
      buflen = buflen + 1
456  
      buffer[buflen] = "{"
457  
      local order = valmeta and valmeta.__jsonorder or globalorder
458  
      if order then
459  
        local used = {}
460  
        n = #order
461  
        for i = 1, n do
462  
          local k = order[i]
463  
          local v = value[k]
464  
          if v then
465  
            used[k] = true
466  
            buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder)
467  
            prev = true -- add a seperator before the next element
468  
          end
469  
        end
470  
        for k,v in pairs (value) do
471  
          if not used[k] then
472  
            buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder)
473  
            if not buflen then return nil, msg end
474  
            prev = true -- add a seperator before the next element
475  
          end
476  
        end
477  
      else -- unordered
478  
        for k,v in pairs (value) do
479  
          buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder)
480  
          if not buflen then return nil, msg end
481  
          prev = true -- add a seperator before the next element
482  
        end
483  
      end
484  
      if indent then
485  
        buflen = addnewline2 (level - 1, buffer, buflen)
486  
      end
487  
      buflen = buflen + 1
488  
      buffer[buflen] = "}"
489  
    end
490  
    tables[value] = nil
491  
  else
492  
    return nil, "type '" .. valtype .. "' is not supported by JSON."
493  
  end
494  
  return buflen
495  
end
496  
497  
function json.encode (value, state)
498  
  state = state or {}
499  
  local oldbuffer = state.buffer
500  
  local buffer = oldbuffer or {}
501  
  updatedecpoint()
502  
  local ret, msg = encode2 (value, state.indent, state.level or 0,
503  
                   buffer, state.bufferlen or 0, state.tables or {}, state.keyorder)
504  
  if not ret then
505  
    error (msg, 2)
506  
  elseif oldbuffer then
507  
    state.bufferlen = ret
508  
    return true
509  
  else
510  
    return concat (buffer)
511  
  end
512  
end
513  
514  
local function loc (str, where)
515  
  local line, pos, linepos = 1, 1, 0
516  
  while true do
517  
    pos = strfind (str, "\n", pos, true)
518  
    if pos and pos < where then
519  
      line = line + 1
520  
      linepos = pos
521  
      pos = pos + 1
522  
    else
523  
      break
524  
    end
525  
  end
526  
  return "line " .. line .. ", column " .. (where - linepos)
527  
end
528  
529  
local function unterminated (str, what, where)
530  
  return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
531  
end
532  
533  
local function scanwhite (str, pos)
534  
  while true do
535  
    pos = strfind (str, "%S", pos)
536  
    if not pos then return nil end
537  
    if strsub (str, pos, pos + 2) == "\239\187\191" then
538  
      -- UTF-8 Byte Order Mark
539  
      pos = pos + 3
540  
    else
541  
      return pos
542  
    end
543  
  end
544  
end
545  
546  
local escapechars = {
547  
  ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
548  
  ["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
549  
}
550  
551  
local function unichar (value)
552  
  if value < 0 then
553  
    return nil
554  
  elseif value <= 0x007f then
555  
    return strchar (value)
556  
  elseif value <= 0x07ff then
557  
    return strchar (0xc0 + floor(value/0x40),
558  
                    0x80 + (floor(value) % 0x40))
559  
  elseif value <= 0xffff then
560  
    return strchar (0xe0 + floor(value/0x1000),
561  
                    0x80 + (floor(value/0x40) % 0x40),
562  
                    0x80 + (floor(value) % 0x40))
563  
  elseif value <= 0x10ffff then
564  
    return strchar (0xf0 + floor(value/0x40000),
565  
                    0x80 + (floor(value/0x1000) % 0x40),
566  
                    0x80 + (floor(value/0x40) % 0x40),
567  
                    0x80 + (floor(value) % 0x40))
568  
  else
569  
    return nil
570  
  end
571  
end
572  
573  
local function scanstring (str, pos)
574  
  local lastpos = pos + 1
575  
  local buffer, n = {}, 0
576  
  while true do
577  
    local nextpos = strfind (str, "[\"\\]", lastpos)
578  
    if not nextpos then
579  
      return unterminated (str, "string", pos)
580  
    end
581  
    if nextpos > lastpos then
582  
      n = n + 1
583  
      buffer[n] = strsub (str, lastpos, nextpos - 1)
584  
    end
585  
    if strsub (str, nextpos, nextpos) == "\"" then
586  
      lastpos = nextpos + 1
587  
      break
588  
    else
589  
      local escchar = strsub (str, nextpos + 1, nextpos + 1)
590  
      local value
591  
      if escchar == "u" then
592  
        value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
593  
        if value then
594  
          local value2
595  
          if 0xD800 <= value and value <= 0xDBff then
596  
            -- we have the high surrogate of UTF-16. Check if there is a
597  
            -- low surrogate escaped nearby to combine them.
598  
            if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
599  
              value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
600  
              if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
601  
                value = (value - 0xD800)  * 0x400 + (value2 - 0xDC00) + 0x10000
602  
              else
603  
                value2 = nil -- in case it was out of range for a low surrogate
604  
              end
605  
            end
606  
          end
607  
          value = value and unichar (value)
608  
          if value then
609  
            if value2 then
610  
              lastpos = nextpos + 12
611  
            else
612  
              lastpos = nextpos + 6
613  
            end
614  
          end
615  
        end
616  
      end
617  
      if not value then
618  
        value = escapechars[escchar] or escchar
619  
        lastpos = nextpos + 2
620  
      end
621  
      n = n + 1
622  
      buffer[n] = value
623  
    end
624  
  end
625  
  if n == 1 then
626  
    return buffer[1], lastpos
627  
  elseif n > 1 then
628  
    return concat (buffer), lastpos
629  
  else
630  
    return "", lastpos
631  
  end
632  
end
633  
634  
local scanvalue -- forward declaration
635  
636  
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
637  
  local len = strlen (str)
638  
  local tbl, n = {}, 0
639  
  local pos = startpos + 1
640  
  if what == 'object' then
641  
    setmetatable (tbl, objectmeta)
642  
  else
643  
    setmetatable (tbl, arraymeta)
644  
  end
645  
  while true do
646  
    pos = scanwhite (str, pos)
647  
    if not pos then return unterminated (str, what, startpos) end
648  
    local char = strsub (str, pos, pos)
649  
    if char == closechar then
650  
      return tbl, pos + 1
651  
    end
652  
    local val1, err
653  
    val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
654  
    if err then return nil, pos, err end
655  
    pos = scanwhite (str, pos)
656  
    if not pos then return unterminated (str, what, startpos) end
657  
    char = strsub (str, pos, pos)
658  
    if char == ":" then
659  
      if val1 == nil then
660  
        return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
661  
      end
662  
      pos = scanwhite (str, pos + 1)
663  
      if not pos then return unterminated (str, what, startpos) end
664  
      local val2
665  
      val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
666  
      if err then return nil, pos, err end
667  
      tbl[val1] = val2
668  
      pos = scanwhite (str, pos)
669  
      if not pos then return unterminated (str, what, startpos) end
670  
      char = strsub (str, pos, pos)
671  
    else
672  
      n = n + 1
673  
      tbl[n] = val1
674  
    end
675  
    if char == "," then
676  
      pos = pos + 1
677  
    end
678  
  end
679  
end
680  
681  
scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
682  
  pos = pos or 1
683  
  pos = scanwhite (str, pos)
684  
  if not pos then
685  
    return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
686  
  end
687  
  local char = strsub (str, pos, pos)
688  
  if char == "{" then
689  
    return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
690  
  elseif char == "[" then
691  
    return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
692  
  elseif char == "\"" then
693  
    return scanstring (str, pos)
694  
  else
695  
    local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
696  
    if pstart then
697  
      local number = str2num (strsub (str, pstart, pend))
698  
      if number then
699  
        return number, pend + 1
700  
      end
701  
    end
702  
    pstart, pend = strfind (str, "^%a%w*", pos)
703  
    if pstart then
704  
      local name = strsub (str, pstart, pend)
705  
      if name == "true" then
706  
        return true, pend + 1
707  
      elseif name == "false" then
708  
        return false, pend + 1
709  
      elseif name == "null" then
710  
        return nullval, pend + 1
711  
      end
712  
    end
713  
    return nil, pos, "no valid JSON value at " .. loc (str, pos)
714  
  end
715  
end
716  
717  
local function optionalmetatables(...)
718  
  if select("#", ...) > 0 then
719  
    return ...
720  
  else
721  
    return {__jsontype = 'object'}, {__jsontype = 'array'}
722  
  end
723  
end
724  
725  
function json.decode (str, pos, nullval, ...)
726  
  local objectmeta, arraymeta = optionalmetatables(...)
727  
  return scanvalue (str, pos, nullval, objectmeta, arraymeta)
728  
end
729  
730  
function json.use_lpeg ()
731  
  local g = require ("lpeg")
732  
733  
  if g.version() == "0.11" then
734  
    error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
735  
  end
736  
737  
  local pegmatch = g.match
738  
  local P, S, R = g.P, g.S, g.R
739  
740  
  local function ErrorCall (str, pos, msg, state)
741  
    if not state.msg then
742  
      state.msg = msg .. " at " .. loc (str, pos)
743  
      state.pos = pos
744  
    end
745  
    return false
746  
  end
747  
748  
  local function Err (msg)
749  
    return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
750  
  end
751  
752  
  local Space = (S" \n\r\t" + P"\239\187\191")^0
753  
754  
  local PlainChar = 1 - S"\"\\\n\r"
755  
  local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
756  
  local HexDigit = R("09", "af", "AF")
757  
  local function UTF16Surrogate (match, pos, high, low)
758  
    high, low = tonumber (high, 16), tonumber (low, 16)
759  
    if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
760  
      return true, unichar ((high - 0xD800)  * 0x400 + (low - 0xDC00) + 0x10000)
761  
    else
762  
      return false
763  
    end
764  
  end
765  
  local function UTF16BMP (hex)
766  
    return unichar (tonumber (hex, 16))
767  
  end
768  
  local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
769  
  local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
770  
  local Char = UnicodeEscape + EscapeSequence + PlainChar
771  
  local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
772  
  local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
773  
  local Fractal = P"." * R"09"^0
774  
  local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
775  
  local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
776  
  local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
777  
  local SimpleValue = Number + String + Constant
778  
  local ArrayContent, ObjectContent
779  
780  
  -- The functions parsearray and parseobject parse only a single value/pair
781  
  -- at a time and store them directly to avoid hitting the LPeg limits.
782  
  local function parsearray (str, pos, nullval, state)
783  
    local obj, cont
784  
    local npos
785  
    local t, nt = {}, 0
786  
    repeat
787  
      obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
788  
      if not npos then break end
789  
      pos = npos
790  
      nt = nt + 1
791  
      t[nt] = obj
792  
    until cont == 'last'
793  
    return pos, setmetatable (t, state.arraymeta)
794  
  end
795  
796  
  local function parseobject (str, pos, nullval, state)
797  
    local obj, key, cont
798  
    local npos
799  
    local t = {}
800  
    repeat
801  
      key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
802  
      if not npos then break end
803  
      pos = npos
804  
      t[key] = obj
805  
    until cont == 'last'
806  
    return pos, setmetatable (t, state.objectmeta)
807  
  end
808  
809  
  local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
810  
  local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
811  
  local Value = Space * (Array + Object + SimpleValue)
812  
  local ExpectedValue = Value + Space * Err "value expected"
813  
  ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
814  
  local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
815  
  ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
816  
  local DecodeValue = ExpectedValue * g.Cp ()
817  
818  
  function json.decode (str, pos, nullval, ...)
819  
    local state = {}
820  
    state.objectmeta, state.arraymeta = optionalmetatables(...)
821  
    local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
822  
    if state.msg then
823  
      return nil, state.pos, state.msg
824  
    else
825  
      return obj, retpos
826  
    end
827  
  end
828  
829  
  -- use this function only once:
830  
  json.use_lpeg = function () return json end
831  
832  
  json.using_lpeg = true
833  
834  
  return json -- so you can get the module using json = require "dkjson".use_lpeg()
835  
end
836  
837  
if always_try_using_lpeg then
838  
  pcall (json.use_lpeg)
839  
end
840  
841  
return json
842  
843  
-->

Author comment

taken verbatim from http://dkolf.de/src/dkjson-lua.fsl/home (loaded 22 Dec 2013)

download  show line numbers   

Travelled to 13 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt, znullqslnikg

Comments [hide]

ID Author/Program Comment Date
934 #1000604 (pitcher) 2015-08-20 15:28:24
933 #1000610 Edit suggestion:
!636
!629

main {
static Object androidContext;
static String programID;

public static void main(String[] args) throws Exception {
-- Module options:
local always_try_using_lpeg = true
local register_global_module_table = false
local global_module_name = 'json'

--[==[

David Kolf's JSON module for Lua 5.1/5.2
========================================

*Version 2.4*

In the default configuration this module writes no global values, not even
the module table. Import it using

json = require ("dkjson")

In environments where `require` or a similiar function are not available
and you cannot receive the return value of the module, you can set the
option `register_global_module_table` to `true`. The module table will
then be saved in the global variable with the name given by the option
`global_module_name`.

Exported functions and values:

`json.encode (object [, state])`
--------------------------------

Create a string representing the object. `Object` can be a table,
a string, a number, a boolean, `nil`, `json.null` or any object with
a function `__tojson` in its metatable. A table can only use strings
and numbers as keys and its values have to be valid objects as
well. It raises an error for any invalid data types or reference
cycles.

`state` is an optional table with the following fields:

- `indent`
When `indent` (a boolean) is set, the created string will contain
newlines and indentations. Otherwise it will be one long line.
- `keyorder`
`keyorder` is an array to specify the ordering of keys in the
encoded output. If an object has keys which are not in this array
they are written after the sorted keys.
- `level`
This is the initial level of indentation used when `indent` is
set. For each level two spaces are added. When absent it is set
to 0.
- `buffer`
`buffer` is an array to store the strings for the result so they
can be concatenated at once. When it isn't given, the encode
function will create it temporary and will return the
concatenated result.
- `bufferlen`
When `bufferlen` is set, it has to be the index of the last
element of `buffer`.
- `tables`
`tables` is a set to detect reference cycles. It is created
temporary when absent. Every table that is currently processed
is used as key, the value is `true`.

When `state.buffer` was set, the return value will be `true` on
success. Without `state.buffer` the return value will be a string.

`json.decode (string [, position [, null]])`
--------------------------------------------

Decode `string` starting at `position` or at 1 if `position` was
omitted.

`null` is an optional value to be returned for null values. The
default is `nil`, but you could set it to `json.null` or any other
value.

The return values are the object or `nil`, the position of the next
character that doesn't belong to the object, and in case of errors
an error message.

Two metatables are created. Every array or object that is decoded gets
a metatable with the `__jsontype` field set to either `array` or
`object`. If you want to provide your own metatables use the syntax

json.decode (string, position, null, objectmeta, arraymeta)

To prevent the assigning of metatables pass `nil`:

json.decode (string, position, null, nil)

`<metatable>.__jsonorder`
-------------------------

`__jsonorder` can overwrite the `keyorder` for a specific table.

`<metatable>.__jsontype`
------------------------

`__jsontype` can be either `"array"` or `"object"`. This value is only
checked for empty tables. (The default for empty tables is `"array"`).

`<metatable>.__tojson (self, state)`
------------------------------------

You can provide your own `__tojson` function in a metatable. In this
function you can either add directly to the buffer and return true,
or you can return a string. On errors nil and a message should be
returned.

`json.null`
-----------

You can use this value for setting explicit `null` values.

`json.version`
--------------

Set to `"dkjson 2.4"`.

`json.quotestring (string)`
---------------------------

Quote a UTF-8 string and escape critical characters using JSON
escape sequences. This function is only necessary when you build
your own `__tojson` functions.

`json.addnewline (state)`
-------------------------

When `state.indent` is set, add a newline to `state.buffer` and spaces
according to `state.level`.

LPeg support
------------

When the local configuration variable `always_try_using_lpeg` is set,
this module tries to load LPeg to replace the `decode` function. The
speed increase is significant. You can get the LPeg module at
<http://www.inf.puc-rio.br/~roberto/lpeg/>.
When LPeg couldn't be loaded, the pure Lua functions stay active.

In case you don't want this module to require LPeg on its own,
disable the option `always_try_using_lpeg` in the options section at
the top of the module.

In this case you can later load LPeg support using

### `json.use_lpeg ()`

Require the LPeg module and replace the functions `quotestring` and
and `decode` with functions that use LPeg patterns.
This function returns the module table, so you can load the module
using:

json = require "dkjson".use_lpeg()

Alternatively you can use `pcall` so the JSON module still works when
LPeg isn't found.

json = require "dkjson"
pcall (json.use_lpeg)

### `json.using_lpeg`

This variable is set to `true` when LPeg was loaded successfully.

---------------------------------------------------------------------

Contact
-------

You can contact the author by sending an e-mail to 'david' at the
domain 'dkolf.de'.

---------------------------------------------------------------------

*Copyright (C) 2010-2013 David Heiko Kolf*

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

<!-- This documentation can be parsed using Markdown to generate HTML.
The source code is enclosed in a HTML comment so it won't be displayed
by browsers, but it should be removed from the final HTML file as
it isn't a valid HTML comment (and wastes space).
-->

<!--]==]

-- global dependencies:
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
local error, require, pcall, select = error, require, pcall, select
local floor, huge = math.floor, math.huge
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
string.rep, string.gsub, string.sub, string.byte, string.char,
string.find, string.len, string.format
local strmatch = string.match
local concat = table.concat

local json = { version = "dkjson 2.4" }

if register_global_module_table then
_G[global_module_name] = json
end

local _ENV = nil -- blocking globals in Lua 5.2

pcall (function()
-- Enable access to blocked metatables.
-- Don't worry, this module doesn't change anything in them.
local debmeta = require "debug".getmetatable
if debmeta then getmetatable = debmeta end
end)

json.null = setmetatable ({}, {
__tojson = function () return "null" end
})

local function isarray (tbl)
local max, n, arraylen = 0, 0, 0
for k,v in pairs (tbl) do
if k == 'n' and type(v) == 'number' then
arraylen = v
if v > max then
max = v
end
else
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
return false
end
if k > max then
max = k
end
n = n + 1
end
end
if max > 10 and max > arraylen and max > n * 2 then
return false -- don't create an array with too many holes
end
return true, max
end

local escapecodes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
}

local function escapeutf8 (uchar)
local value = escapecodes[uchar]
if value then
return value
end
local a, b, c, d = strbyte (uchar, 1, 4)
a, b, c, d = a or 0, b or 0, c or 0, d or 0
if a <= 0x7f then
value = a
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
value = (a - 0xc0) * 0x40 + b - 0x80
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
else
return ""
end
if value <= 0xffff then
return strformat ("\\u%.4x", value)
elseif value <= 0x10ffff then
-- encode as UTF-16 surrogate pair
value = value - 0x10000
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
else
return ""
end
end

local function fsub (str, pattern, repl)
-- gsub always builds a new string in a buffer, even when no match
-- exists. First using find should be more efficient when most strings
-- don't contain the pattern.
if strfind (str, pattern) then
return gsub (str, pattern, repl)
else
return str
end
end

local function quotestring (value)
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
if strfind (value, "[\194\216\220\225\226\239]") then
value = fsub (value, "\194[\128-\159\173]", escapeutf8)
value = fsub (value, "\216[\128-\132]", escapeutf8)
value = fsub (value, "\220\143", escapeutf8)
value = fsub (value, "\225\158[\180\181]", escapeutf8)
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
value = fsub (value, "\226\129[\160-\175]", escapeutf8)
value = fsub (value, "\239\187\191", escapeutf8)
value = fsub (value, "\239\191[\176-\191]", escapeutf8)
end
return "\"" .. value .. "\""
end
json.quotestring = quotestring

local function replace(str, o, n)
local i, j = strfind (str, o, 1, true)
if i then
return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
else
return str
end
end

-- locale independent num2str and str2num functions
local decpoint, numfilter

local function updatedecpoint ()
decpoint = strmatch(tostring(0.5), "([^05+])")
-- build a filter that can be used to remove group separators
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
end

updatedecpoint()

local function num2str (num)
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
end

local function str2num (str)
local num = tonumber(replace(str, ".", decpoint))
if not num then
updatedecpoint()
num = tonumber(replace(str, ".", decpoint))
end
return num
end

local function addnewline2 (level, buffer, buflen)
buffer[buflen+1] = "\n"
buffer[buflen+2] = strrep (" ", level)
buflen = buflen + 2
return buflen
end

function json.addnewline (state)
if state.indent then
state.bufferlen = addnewline2 (state.level or 0,
state.buffer, state.bufferlen or #(state.buffer))
end
end

local encode2 -- forward declaration

local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder)
local kt = type (key)
if kt ~= 'string' and kt ~= 'number' then
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
end
if prev then
buflen = buflen + 1
buffer[buflen] = ","
end
if indent then
buflen = addnewline2 (level, buffer, buflen)
end
buffer[buflen+1] = quotestring (key)
buffer[buflen+2] = ":"
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder)
end

encode2 = function (value, indent, level, buffer, buflen, tables, globalorder)
local valtype = type (value)
local valmeta = getmetatable (value)
valmeta = type (valmeta) == 'table' and valmeta -- only tables
local valtojson = valmeta and valmeta.__tojson
if valtojson then
if tables[value] then
return nil, "reference cycle"
end
tables[value] = true
local state = {
indent = indent, level = level, buffer = buffer,
bufferlen = buflen, tables = tables, keyorder = globalorder
}
local ret, msg = valtojson (value, state)
if not ret then return nil, msg end
tables[value] = nil
buflen = state.bufferlen
if type (ret) == 'string' then
buflen = buflen + 1
buffer[buflen] = ret
end
elseif value == nil then
buflen = buflen + 1
buffer[buflen] = "null"
elseif valtype == 'number' then
local s
if value ~= value or value >= huge or -value >= huge then
-- This is the behaviour of the original JSON implementation.
s = "null"
else
s = num2str (value)
end
buflen = buflen + 1
buffer[buflen] = s
elseif valtype == 'boolean' then
buflen = buflen + 1
buffer[buflen] = value and "true" or "false"
elseif valtype == 'string' then
buflen = buflen + 1
buffer[buflen] = quotestring (value)
elseif valtype == 'table' then
if tables[value] then
return nil, "reference cycle"
end
tables[value] = true
level = level + 1
local isa, n = isarray (value)
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
isa = false
end
local msg
if isa then -- JSON array
buflen = buflen + 1
buffer[buflen] = "["
for i = 1, n do
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder)
if not buflen then return nil, msg end
if i < n then
buflen = buflen + 1
buffer[buflen] = ","
end
end
buflen = buflen + 1
buffer[buflen] = "]"
else -- JSON object
local prev = false
buflen = buflen + 1
buffer[buflen] = "{"
local order = valmeta and valmeta.__jsonorder or globalorder
if order then
local used = {}
n = #order
for i = 1, n do
local k = order[i]
local v = value[k]
if v then
used[k] = true
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder)
prev = true -- add a seperator before the next element
end
end
for k,v in pairs (value) do
if not used[k] then
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
else -- unordered
for k,v in pairs (value) do
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
if indent then
buflen = addnewline2 (level - 1, buffer, buflen)
end
buflen = buflen + 1
buffer[buflen] = "}"
end
tables[value] = nil
else
return nil, "type '" .. valtype .. "' is not supported by JSON."
end
return buflen
end

function json.encode (value, state)
state = state or {}
local oldbuffer = state.buffer
local buffer = oldbuffer or {}
updatedecpoint()
local ret, msg = encode2 (value, state.indent, state.level or 0,
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder)
if not ret then
error (msg, 2)
elseif oldbuffer then
state.bufferlen = ret
return true
else
return concat (buffer)
end
end

local function loc (str, where)
local line, pos, linepos = 1, 1, 0
while true do
pos = strfind (str, "\n", pos, true)
if pos and pos < where then
line = line + 1
linepos = pos
pos = pos + 1
else
break
end
end
return "line " .. line .. ", column " .. (where - linepos)
end

local function unterminated (str, what, where)
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
end

local function scanwhite (str, pos)
while true do
pos = strfind (str, "%S", pos)
if not pos then return nil end
if strsub (str, pos, pos + 2) == "\239\187\191" then
-- UTF-8 Byte Order Mark
pos = pos + 3
else
return pos
end
end
end

local escapechars = {
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
}

local function unichar (value)
if value < 0 then
return nil
elseif value <= 0x007f then
return strchar (value)
elseif value <= 0x07ff then
return strchar (0xc0 + floor(value/0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0xffff then
return strchar (0xe0 + floor(value/0x1000),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0x10ffff then
return strchar (0xf0 + floor(value/0x40000),
0x80 + (floor(value/0x1000) % 0x40),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
else
return nil
end
end

local function scanstring (str, pos)
local lastpos = pos + 1
local buffer, n = {}, 0
while true do
local nextpos = strfind (str, "[\"\\]", lastpos)
if not nextpos then
return unterminated (str, "string", pos)
end
if nextpos > lastpos then
n = n + 1
buffer[n] = strsub (str, lastpos, nextpos - 1)
end
if strsub (str, nextpos, nextpos) == "\"" then
lastpos = nextpos + 1
break
else
local escchar = strsub (str, nextpos + 1, nextpos + 1)
local value
if escchar == "u" then
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
if value then
local value2
if 0xD800 <= value and value <= 0xDBff then
-- we have the high surrogate of UTF-16. Check if there is a
-- low surrogate escaped nearby to combine them.
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
else
value2 = nil -- in case it was out of range for a low surrogate
end
end
end
value = value and unichar (value)
if value then
if value2 then
lastpos = nextpos + 12
else
lastpos = nextpos + 6
end
end
end
end
if not value then
value = escapechars[escchar] or escchar
lastpos = nextpos + 2
end
n = n + 1
buffer[n] = value
end
end
if n == 1 then
return buffer[1], lastpos
elseif n > 1 then
return concat (buffer), lastpos
else
return "", lastpos
end
end

local scanvalue -- forward declaration

local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
local len = strlen (str)
local tbl, n = {}, 0
local pos = startpos + 1
if what == 'object' then
setmetatable (tbl, objectmeta)
else
setmetatable (tbl, arraymeta)
end
while true do
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
local char = strsub (str, pos, pos)
if char == closechar then
return tbl, pos + 1
end
local val1, err
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
if char == ":" then
if val1 == nil then
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
end
pos = scanwhite (str, pos + 1)
if not pos then return unterminated (str, what, startpos) end
local val2
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
tbl[val1] = val2
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
else
n = n + 1
tbl[n] = val1
end
if char == "," then
pos = pos + 1
end
end
end

scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
pos = pos or 1
pos = scanwhite (str, pos)
if not pos then
return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
end
local char = strsub (str, pos, pos)
if char == "{" then
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
elseif char == "[" then
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
elseif char == "\"" then
return scanstring (str, pos)
else
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
if pstart then
local number = str2num (strsub (str, pstart, pend))
if number then
return number, pend + 1
end
end
pstart, pend = strfind (str, "^%a%w*", pos)
if pstart then
local name = strsub (str, pstart, pend)
if name == "true" then
return true, pend + 1
elseif name == "false" then
return false, pend + 1
elseif name == "null" then
return nullval, pend + 1
end
end
return nil, pos, "no valid JSON value at " .. loc (str, pos)
end
end

local function optionalmetatables(...)
if select("#", ...) > 0 then
return ...
else
return {__jsontype = 'object'}, {__jsontype = 'array'}
end
end

function json.decode (str, pos, nullval, ...)
local objectmeta, arraymeta = optionalmetatables(...)
return scanvalue (str, pos, nullval, objectmeta, arraymeta)
end

function json.use_lpeg ()
local g = require ("lpeg")

if g.version() == "0.11" then
error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
end

local pegmatch = g.match
local P, S, R = g.P, g.S, g.R

local function ErrorCall (str, pos, msg, state)
if not state.msg then
state.msg = msg .. " at " .. loc (str, pos)
state.pos = pos
end
return false
end

local function Err (msg)
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
end

local Space = (S" \n\r\t" + P"\239\187\191")^0

local PlainChar = 1 - S"\"\\\n\r"
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
local HexDigit = R("09", "af", "AF")
local function UTF16Surrogate (match, pos, high, low)
high, low = tonumber (high, 16), tonumber (low, 16)
if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
else
return false
end
end
local function UTF16BMP (hex)
return unichar (tonumber (hex, 16))
end
local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
local Char = UnicodeEscape + EscapeSequence + PlainChar
local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
local Fractal = P"." * R"09"^0
local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
local SimpleValue = Number + String + Constant
local ArrayContent, ObjectContent

-- The functions parsearray and parseobject parse only a single value/pair
-- at a time and store them directly to avoid hitting the LPeg limits.
local function parsearray (str, pos, nullval, state)
local obj, cont
local npos
local t, nt = {}, 0
repeat
obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
if not npos then break end
pos = npos
nt = nt + 1
t[nt] = obj
until cont == 'last'
return pos, setmetatable (t, state.arraymeta)
end

local function parseobject (str, pos, nullval, state)
local obj, key, cont
local npos
local t = {}
repeat
key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
if not npos then break end
pos = npos
t[key] = obj
until cont == 'last'
return pos, setmetatable (t, state.objectmeta)
end

local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
local Value = Space * (Array + Object + SimpleValue)
local ExpectedValue = Value + Space * Err "value expected"
ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
local DecodeValue = ExpectedValue * g.Cp ()

function json.decode (str, pos, nullval, ...)
local state = {}
state.objectmeta, state.arraymeta = optionalmetatables(...)
local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
if state.msg then
return nil, state.pos, state.msg
else
return obj, retpos
end
end

-- use this function only once:
json.use_lpeg = function () return json end

json.using_lpeg = true

return json -- so you can get the module using json = require "dkjson".use_lpeg()
end

if always_try_using_lpeg then
pcall (json.use_lpeg)
end

return json

-->
}}
2015-08-20 05:49:11  delete 

add comment

Image recognition results

Recognizer Recognition Result Visualize Recalc
#308 27434 [visualize]

Snippet ID: #44
Snippet name: dkjson
Eternal ID of this version: #44/1
Text MD5: 1e9ad6209dbe9669f78ba5694022fb5e
Author: stefan
Category:
Type: Source file
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2013-12-22 13:48:02
Source code size: 27434 bytes / 843 lines
Pitched / IR pitched: No / Yes
Views / Downloads: 1847 / 621
Referenced in: [show references]