]> git.madduck.net Git - etc/awesome.git/blob - util/dkjson.lua

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

weather: icons based on localtime; closes pull #198
[etc/awesome.git] / util / dkjson.lua
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 Version 2.5
11
12
13 For the documentation see the corresponding readme.txt or visit
14 <http://dkolf.de/src/dkjson-lua.fsl/>.
15
16 You can contact the author by sending an e-mail to 'david' at the
17 domain 'dkolf.de'.
18
19
20 Copyright (C) 2010-2013 David Heiko Kolf
21
22 Permission is hereby granted, free of charge, to any person obtaining
23 a copy of this software and associated documentation files (the
24 "Software"), to deal in the Software without restriction, including
25 without limitation the rights to use, copy, modify, merge, publish,
26 distribute, sublicense, and/or sell copies of the Software, and to
27 permit persons to whom the Software is furnished to do so, subject to
28 the following conditions:
29
30 The above copyright notice and this permission notice shall be
31 included in all copies or substantial portions of the Software.
32
33 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
34 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
35 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
36 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
37 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
38 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
39 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40 SOFTWARE.
41
42 --]==]
43
44 -- global dependencies:
45 local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
46       pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
47 local error, require, pcall, select = error, require, pcall, select
48 local floor, huge = math.floor, math.huge
49 local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
50       string.rep, string.gsub, string.sub, string.byte, string.char,
51       string.find, string.len, string.format
52 local strmatch = string.match
53 local concat = table.concat
54
55 local json = { version = "dkjson 2.5" }
56
57 if register_global_module_table then
58   _G[global_module_name] = json
59 end
60
61 local _ENV = nil -- blocking globals in Lua 5.2
62
63 pcall (function()
64   -- Enable access to blocked metatables.
65   -- Don't worry, this module doesn't change anything in them.
66   local debmeta = require "debug".getmetatable
67   if debmeta then getmetatable = debmeta end
68 end)
69
70 json.null = setmetatable ({}, {
71   __tojson = function () return "null" end
72 })
73
74 local function isarray (tbl)
75   local max, n, arraylen = 0, 0, 0
76   for k,v in pairs (tbl) do
77     if k == 'n' and type(v) == 'number' then
78       arraylen = v
79       if v > max then
80         max = v
81       end
82     else
83       if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
84         return false
85       end
86       if k > max then
87         max = k
88       end
89       n = n + 1
90     end
91   end
92   if max > 10 and max > arraylen and max > n * 2 then
93     return false -- don't create an array with too many holes
94   end
95   return true, max
96 end
97
98 local escapecodes = {
99   ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
100   ["\n"] = "\\n",  ["\r"] = "\\r",  ["\t"] = "\\t"
101 }
102
103 local function escapeutf8 (uchar)
104   local value = escapecodes[uchar]
105   if value then
106     return value
107   end
108   local a, b, c, d = strbyte (uchar, 1, 4)
109   a, b, c, d = a or 0, b or 0, c or 0, d or 0
110   if a <= 0x7f then
111     value = a
112   elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
113     value = (a - 0xc0) * 0x40 + b - 0x80
114   elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
115     value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
116   elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
117     value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
118   else
119     return ""
120   end
121   if value <= 0xffff then
122     return strformat ("\\u%.4x", value)
123   elseif value <= 0x10ffff then
124     -- encode as UTF-16 surrogate pair
125     value = value - 0x10000
126     local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
127     return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
128   else
129     return ""
130   end
131 end
132
133 local function fsub (str, pattern, repl)
134   -- gsub always builds a new string in a buffer, even when no match
135   -- exists. First using find should be more efficient when most strings
136   -- don't contain the pattern.
137   if strfind (str, pattern) then
138     return gsub (str, pattern, repl)
139   else
140     return str
141   end
142 end
143
144 local function quotestring (value)
145   -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
146   value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
147   if strfind (value, "[\194\216\220\225\226\239]") then
148     value = fsub (value, "\194[\128-\159\173]", escapeutf8)
149     value = fsub (value, "\216[\128-\132]", escapeutf8)
150     value = fsub (value, "\220\143", escapeutf8)
151     value = fsub (value, "\225\158[\180\181]", escapeutf8)
152     value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
153     value = fsub (value, "\226\129[\160-\175]", escapeutf8)
154     value = fsub (value, "\239\187\191", escapeutf8)
155     value = fsub (value, "\239\191[\176-\191]", escapeutf8)
156   end
157   return "\"" .. value .. "\""
158 end
159 json.quotestring = quotestring
160
161 local function replace(str, o, n)
162   local i, j = strfind (str, o, 1, true)
163   if i then
164     return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
165   else
166     return str
167   end
168 end
169
170 -- locale independent num2str and str2num functions
171 local decpoint, numfilter
172
173 local function updatedecpoint ()
174   decpoint = strmatch(tostring(0.5), "([^05+])")
175   -- build a filter that can be used to remove group separators
176   numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
177 end
178
179 updatedecpoint()
180
181 local function num2str (num)
182   return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
183 end
184
185 local function str2num (str)
186   local num = tonumber(replace(str, ".", decpoint))
187   if not num then
188     updatedecpoint()
189     num = tonumber(replace(str, ".", decpoint))
190   end
191   return num
192 end
193
194 local function addnewline2 (level, buffer, buflen)
195   buffer[buflen+1] = "\n"
196   buffer[buflen+2] = strrep ("  ", level)
197   buflen = buflen + 2
198   return buflen
199 end
200
201 function json.addnewline (state)
202   if state.indent then
203     state.bufferlen = addnewline2 (state.level or 0,
204                            state.buffer, state.bufferlen or #(state.buffer))
205   end
206 end
207
208 local encode2 -- forward declaration
209
210 local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
211   local kt = type (key)
212   if kt ~= 'string' and kt ~= 'number' then
213     return nil, "type '" .. kt .. "' is not supported as a key by JSON."
214   end
215   if prev then
216     buflen = buflen + 1
217     buffer[buflen] = ","
218   end
219   if indent then
220     buflen = addnewline2 (level, buffer, buflen)
221   end
222   buffer[buflen+1] = quotestring (key)
223   buffer[buflen+2] = ":"
224   return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
225 end
226
227 local function appendcustom(res, buffer, state)
228   local buflen = state.bufferlen
229   if type (res) == 'string' then
230     buflen = buflen + 1
231     buffer[buflen] = res
232   end
233   return buflen
234 end
235
236 local function exception(reason, value, state, buffer, buflen, defaultmessage)
237   defaultmessage = defaultmessage or reason
238   local handler = state.exception
239   if not handler then
240     return nil, defaultmessage
241   else
242     state.bufferlen = buflen
243     local ret, msg = handler (reason, value, state, defaultmessage)
244     if not ret then return nil, msg or defaultmessage end
245     return appendcustom(ret, buffer, state)
246   end
247 end
248
249 function json.encodeexception(reason, value, state, defaultmessage)
250   return quotestring("<" .. defaultmessage .. ">")
251 end
252
253 encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
254   local valtype = type (value)
255   local valmeta = getmetatable (value)
256   valmeta = type (valmeta) == 'table' and valmeta -- only tables
257   local valtojson = valmeta and valmeta.__tojson
258   if valtojson then
259     if tables[value] then
260       return exception('reference cycle', value, state, buffer, buflen)
261     end
262     tables[value] = true
263     state.bufferlen = buflen
264     local ret, msg = valtojson (value, state)
265     if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
266     tables[value] = nil
267     buflen = appendcustom(ret, buffer, state)
268   elseif value == nil then
269     buflen = buflen + 1
270     buffer[buflen] = "null"
271   elseif valtype == 'number' then
272     local s
273     if value ~= value or value >= huge or -value >= huge then
274       -- This is the behaviour of the original JSON implementation.
275       s = "null"
276     else
277       s = num2str (value)
278     end
279     buflen = buflen + 1
280     buffer[buflen] = s
281   elseif valtype == 'boolean' then
282     buflen = buflen + 1
283     buffer[buflen] = value and "true" or "false"
284   elseif valtype == 'string' then
285     buflen = buflen + 1
286     buffer[buflen] = quotestring (value)
287   elseif valtype == 'table' then
288     if tables[value] then
289       return exception('reference cycle', value, state, buffer, buflen)
290     end
291     tables[value] = true
292     level = level + 1
293     local isa, n = isarray (value)
294     if n == 0 and valmeta and valmeta.__jsontype == 'object' then
295       isa = false
296     end
297     local msg
298     if isa then -- JSON array
299       buflen = buflen + 1
300       buffer[buflen] = "["
301       for i = 1, n do
302         buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
303         if not buflen then return nil, msg end
304         if i < n then
305           buflen = buflen + 1
306           buffer[buflen] = ","
307         end
308       end
309       buflen = buflen + 1
310       buffer[buflen] = "]"
311     else -- JSON object
312       local prev = false
313       buflen = buflen + 1
314       buffer[buflen] = "{"
315       local order = valmeta and valmeta.__jsonorder or globalorder
316       if order then
317         local used = {}
318         n = #order
319         for i = 1, n do
320           local k = order[i]
321           local v = value[k]
322           if v then
323             used[k] = true
324             buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
325             prev = true -- add a seperator before the next element
326           end
327         end
328         for k,v in pairs (value) do
329           if not used[k] then
330             buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
331             if not buflen then return nil, msg end
332             prev = true -- add a seperator before the next element
333           end
334         end
335       else -- unordered
336         for k,v in pairs (value) do
337           buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
338           if not buflen then return nil, msg end
339           prev = true -- add a seperator before the next element
340         end
341       end
342       if indent then
343         buflen = addnewline2 (level - 1, buffer, buflen)
344       end
345       buflen = buflen + 1
346       buffer[buflen] = "}"
347     end
348     tables[value] = nil
349   else
350     return exception ('unsupported type', value, state, buffer, buflen,
351       "type '" .. valtype .. "' is not supported by JSON.")
352   end
353   return buflen
354 end
355
356 function json.encode (value, state)
357   state = state or {}
358   local oldbuffer = state.buffer
359   local buffer = oldbuffer or {}
360   state.buffer = buffer
361   updatedecpoint()
362   local ret, msg = encode2 (value, state.indent, state.level or 0,
363                    buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
364   if not ret then
365     error (msg, 2)
366   elseif oldbuffer == buffer then
367     state.bufferlen = ret
368     return true
369   else
370     state.bufferlen = nil
371     state.buffer = nil
372     return concat (buffer)
373   end
374 end
375
376 local function loc (str, where)
377   local line, pos, linepos = 1, 1, 0
378   while true do
379     pos = strfind (str, "\n", pos, true)
380     if pos and pos < where then
381       line = line + 1
382       linepos = pos
383       pos = pos + 1
384     else
385       break
386     end
387   end
388   return "line " .. line .. ", column " .. (where - linepos)
389 end
390
391 local function unterminated (str, what, where)
392   return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
393 end
394
395 local function scanwhite (str, pos)
396   while true do
397     pos = strfind (str, "%S", pos)
398     if not pos then return nil end
399     local sub2 = strsub (str, pos, pos + 1)
400     if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
401       -- UTF-8 Byte Order Mark
402       pos = pos + 3
403     elseif sub2 == "//" then
404       pos = strfind (str, "[\n\r]", pos + 2)
405       if not pos then return nil end
406     elseif sub2 == "/*" then
407       pos = strfind (str, "*/", pos + 2)
408       if not pos then return nil end
409       pos = pos + 2
410     else
411       return pos
412     end
413   end
414 end
415
416 local escapechars = {
417   ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
418   ["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
419 }
420
421 local function unichar (value)
422   if value < 0 then
423     return nil
424   elseif value <= 0x007f then
425     return strchar (value)
426   elseif value <= 0x07ff then
427     return strchar (0xc0 + floor(value/0x40),
428                     0x80 + (floor(value) % 0x40))
429   elseif value <= 0xffff then
430     return strchar (0xe0 + floor(value/0x1000),
431                     0x80 + (floor(value/0x40) % 0x40),
432                     0x80 + (floor(value) % 0x40))
433   elseif value <= 0x10ffff then
434     return strchar (0xf0 + floor(value/0x40000),
435                     0x80 + (floor(value/0x1000) % 0x40),
436                     0x80 + (floor(value/0x40) % 0x40),
437                     0x80 + (floor(value) % 0x40))
438   else
439     return nil
440   end
441 end
442
443 local function scanstring (str, pos)
444   local lastpos = pos + 1
445   local buffer, n = {}, 0
446   while true do
447     local nextpos = strfind (str, "[\"\\]", lastpos)
448     if not nextpos then
449       return unterminated (str, "string", pos)
450     end
451     if nextpos > lastpos then
452       n = n + 1
453       buffer[n] = strsub (str, lastpos, nextpos - 1)
454     end
455     if strsub (str, nextpos, nextpos) == "\"" then
456       lastpos = nextpos + 1
457       break
458     else
459       local escchar = strsub (str, nextpos + 1, nextpos + 1)
460       local value
461       if escchar == "u" then
462         value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
463         if value then
464           local value2
465           if 0xD800 <= value and value <= 0xDBff then
466             -- we have the high surrogate of UTF-16. Check if there is a
467             -- low surrogate escaped nearby to combine them.
468             if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
469               value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
470               if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
471                 value = (value - 0xD800)  * 0x400 + (value2 - 0xDC00) + 0x10000
472               else
473                 value2 = nil -- in case it was out of range for a low surrogate
474               end
475             end
476           end
477           value = value and unichar (value)
478           if value then
479             if value2 then
480               lastpos = nextpos + 12
481             else
482               lastpos = nextpos + 6
483             end
484           end
485         end
486       end
487       if not value then
488         value = escapechars[escchar] or escchar
489         lastpos = nextpos + 2
490       end
491       n = n + 1
492       buffer[n] = value
493     end
494   end
495   if n == 1 then
496     return buffer[1], lastpos
497   elseif n > 1 then
498     return concat (buffer), lastpos
499   else
500     return "", lastpos
501   end
502 end
503
504 local scanvalue -- forward declaration
505
506 local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
507   local len = strlen (str)
508   local tbl, n = {}, 0
509   local pos = startpos + 1
510   if what == 'object' then
511     setmetatable (tbl, objectmeta)
512   else
513     setmetatable (tbl, arraymeta)
514   end
515   while true do
516     pos = scanwhite (str, pos)
517     if not pos then return unterminated (str, what, startpos) end
518     local char = strsub (str, pos, pos)
519     if char == closechar then
520       return tbl, pos + 1
521     end
522     local val1, err
523     val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
524     if err then return nil, pos, err end
525     pos = scanwhite (str, pos)
526     if not pos then return unterminated (str, what, startpos) end
527     char = strsub (str, pos, pos)
528     if char == ":" then
529       if val1 == nil then
530         return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
531       end
532       pos = scanwhite (str, pos + 1)
533       if not pos then return unterminated (str, what, startpos) end
534       local val2
535       val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
536       if err then return nil, pos, err end
537       tbl[val1] = val2
538       pos = scanwhite (str, pos)
539       if not pos then return unterminated (str, what, startpos) end
540       char = strsub (str, pos, pos)
541     else
542       n = n + 1
543       tbl[n] = val1
544     end
545     if char == "," then
546       pos = pos + 1
547     end
548   end
549 end
550
551 scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
552   pos = pos or 1
553   pos = scanwhite (str, pos)
554   if not pos then
555     return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
556   end
557   local char = strsub (str, pos, pos)
558   if char == "{" then
559     return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
560   elseif char == "[" then
561     return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
562   elseif char == "\"" then
563     return scanstring (str, pos)
564   else
565     local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
566     if pstart then
567       local number = str2num (strsub (str, pstart, pend))
568       if number then
569         return number, pend + 1
570       end
571     end
572     pstart, pend = strfind (str, "^%a%w*", pos)
573     if pstart then
574       local name = strsub (str, pstart, pend)
575       if name == "true" then
576         return true, pend + 1
577       elseif name == "false" then
578         return false, pend + 1
579       elseif name == "null" then
580         return nullval, pend + 1
581       end
582     end
583     return nil, pos, "no valid JSON value at " .. loc (str, pos)
584   end
585 end
586
587 local function optionalmetatables(...)
588   if select("#", ...) > 0 then
589     return ...
590   else
591     return {__jsontype = 'object'}, {__jsontype = 'array'}
592   end
593 end
594
595 function json.decode (str, pos, nullval, ...)
596   local objectmeta, arraymeta = optionalmetatables(...)
597   return scanvalue (str, pos, nullval, objectmeta, arraymeta)
598 end
599
600 function json.use_lpeg ()
601   local g = require ("lpeg")
602
603   if g.version() == "0.11" then
604     error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
605   end
606
607   local pegmatch = g.match
608   local P, S, R = g.P, g.S, g.R
609
610   local function ErrorCall (str, pos, msg, state)
611     if not state.msg then
612       state.msg = msg .. " at " .. loc (str, pos)
613       state.pos = pos
614     end
615     return false
616   end
617
618   local function Err (msg)
619     return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
620   end
621
622   local SingleLineComment = P"//" * (1 - S"\n\r")^0
623   local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
624   local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
625
626   local PlainChar = 1 - S"\"\\\n\r"
627   local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
628   local HexDigit = R("09", "af", "AF")
629   local function UTF16Surrogate (match, pos, high, low)
630     high, low = tonumber (high, 16), tonumber (low, 16)
631     if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
632       return true, unichar ((high - 0xD800)  * 0x400 + (low - 0xDC00) + 0x10000)
633     else
634       return false
635     end
636   end
637   local function UTF16BMP (hex)
638     return unichar (tonumber (hex, 16))
639   end
640   local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
641   local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
642   local Char = UnicodeEscape + EscapeSequence + PlainChar
643   local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
644   local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
645   local Fractal = P"." * R"09"^0
646   local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
647   local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
648   local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
649   local SimpleValue = Number + String + Constant
650   local ArrayContent, ObjectContent
651
652   -- The functions parsearray and parseobject parse only a single value/pair
653   -- at a time and store them directly to avoid hitting the LPeg limits.
654   local function parsearray (str, pos, nullval, state)
655     local obj, cont
656     local npos
657     local t, nt = {}, 0
658     repeat
659       obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
660       if not npos then break end
661       pos = npos
662       nt = nt + 1
663       t[nt] = obj
664     until cont == 'last'
665     return pos, setmetatable (t, state.arraymeta)
666   end
667
668   local function parseobject (str, pos, nullval, state)
669     local obj, key, cont
670     local npos
671     local t = {}
672     repeat
673       key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
674       if not npos then break end
675       pos = npos
676       t[key] = obj
677     until cont == 'last'
678     return pos, setmetatable (t, state.objectmeta)
679   end
680
681   local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
682   local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
683   local Value = Space * (Array + Object + SimpleValue)
684   local ExpectedValue = Value + Space * Err "value expected"
685   ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
686   local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
687   ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
688   local DecodeValue = ExpectedValue * g.Cp ()
689
690   function json.decode (str, pos, nullval, ...)
691     local state = {}
692     state.objectmeta, state.arraymeta = optionalmetatables(...)
693     local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
694     if state.msg then
695       return nil, state.pos, state.msg
696     else
697       return obj, retpos
698     end
699   end
700
701   -- use this function only once:
702   json.use_lpeg = function () return json end
703
704   json.using_lpeg = true
705
706   return json -- so you can get the module using json = require "dkjson".use_lpeg()
707 end
708
709 if always_try_using_lpeg then
710   pcall (json.use_lpeg)
711 end
712
713 return json