Advertisement
NeOzay

dkjson.lua

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