· 6 years ago · Sep 21, 2019, 12:50 PM
1 -- Module options:
2 local always_try_using_lpeg = false
3 local register_global_module_table = true
4 local global_module_name = 'json'
5
6 --[==[
7
8David Kolf's JSON module for Lua 5.1/5.2
9========================================
10
11*Version 2.4*
12
13In the default configuration this module writes no global values, not even
14the module table. Import it using
15
16 json = require ("dkjson")
17
18In environments where `require` or a similiar function are not available
19and you cannot receive the return value of the module, you can set the
20option `register_global_module_table` to `true`. The module table will
21then be saved in the global variable with the name given by the option
22`global_module_name`.
23
24Exported functions and values:
25
26`json.encode (object [, state])`
27--------------------------------
28
29Create a string representing the object. `Object` can be a table,
30a string, a number, a boolean, `nil`, `json.null` or any object with
31a function `__tojson` in its metatable. A table can only use strings
32and numbers as keys and its values have to be valid objects as
33well. It raises an error for any invalid data types or reference
34cycles.
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
62When `state.buffer` was set, the return value will be `true` on
63success. Without `state.buffer` the return value will be a string.
64
65`json.decode (string [, position [, null]])`
66--------------------------------------------
67
68Decode `string` starting at `position` or at 1 if `position` was
69omitted.
70
71`null` is an optional value to be returned for null values. The
72default is `nil`, but you could set it to `json.null` or any other
73value.
74
75The return values are the object or `nil`, the position of the next
76character that doesn't belong to the object, and in case of errors
77an error message.
78
79Two metatables are created. Every array or object that is decoded gets
80a 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
85To 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
98checked for empty tables. (The default for empty tables is `"array"`).
99
100`<metatable>.__tojson (self, state)`
101------------------------------------
102
103You can provide your own `__tojson` function in a metatable. In this
104function you can either add directly to the buffer and return true,
105or you can return a string. On errors nil and a message should be
106returned.
107
108`json.null`
109-----------
110
111You can use this value for setting explicit `null` values.
112
113`json.version`
114--------------
115
116Set to `"dkjson 2.4"`.
117
118`json.quotestring (string)`
119---------------------------
120
121Quote a UTF-8 string and escape critical characters using JSON
122escape sequences. This function is only necessary when you build
123your own `__tojson` functions.
124
125`json.addnewline (state)`
126-------------------------
127
128When `state.indent` is set, add a newline to `state.buffer` and spaces
129according to `state.level`.
130
131LPeg support
132------------
133
134When the local configuration variable `always_try_using_lpeg` is set,
135this module tries to load LPeg to replace the `decode` function. The
136speed increase is significant. You can get the LPeg module at
137 <http://www.inf.puc-rio.br/~roberto/lpeg/>.
138When LPeg couldn't be loaded, the pure Lua functions stay active.
139
140In case you don't want this module to require LPeg on its own,
141disable the option `always_try_using_lpeg` in the options section at
142the top of the module.
143
144In this case you can later load LPeg support using
145
146### `json.use_lpeg ()`
147
148Require the LPeg module and replace the functions `quotestring` and
149and `decode` with functions that use LPeg patterns.
150This function returns the module table, so you can load the module
151using:
152
153 json = require "dkjson".use_lpeg()
154
155Alternatively you can use `pcall` so the JSON module still works when
156LPeg isn't found.
157
158 json = require "dkjson"
159 pcall (json.use_lpeg)
160
161### `json.using_lpeg`
162
163This variable is set to `true` when LPeg was loaded successfully.
164
165---------------------------------------------------------------------
166
167Contact
168-------
169
170You can contact the author by sending an e-mail to 'david' at the
171domain 'dkolf.de'.
172
173---------------------------------------------------------------------
174
175*Copyright (C) 2010-2013 David Heiko Kolf*
176
177Permission is hereby granted, free of charge, to any person obtaining
178a copy of this software and associated documentation files (the
179"Software"), to deal in the Software without restriction, including
180without limitation the rights to use, copy, modify, merge, publish,
181distribute, sublicense, and/or sell copies of the Software, and to
182permit persons to whom the Software is furnished to do so, subject to
183the following conditions:
184
185The above copyright notice and this permission notice shall be
186included in all copies or substantial portions of the Software.
187
188THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
189EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
190MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
191NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
192BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
193ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
194CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
195SOFTWARE.
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:
206local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
207 pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
208local error, require, pcall, select = error, require, pcall, select
209local floor, huge = math.floor, math.huge
210local 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
213local strmatch = string.match
214local concat = table.concat
215
216local json = { version = "dkjson 2.4" }
217
218if register_global_module_table then
219 _G[global_module_name] = json
220end
221
222local _ENV = nil -- blocking globals in Lua 5.2
223
224pcall (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
229end)
230
231json.null = setmetatable ({}, {
232 __tojson = function () return "null" end
233})
234
235local 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
257end
258
259local escapecodes = {
260 ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
261 ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
262}
263
264local 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
292end
293
294local 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
303end
304
305local 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 .. "\""
319end
320json.quotestring = quotestring
321
322local 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
329end
330
331-- locale independent num2str and str2num functions
332local decpoint, numfilter
333
334local 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") .. "]+"
338end
339
340updatedecpoint()
341
342local function num2str (num)
343 return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
344end
345
346local 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
353end
354
355local function addnewline2 (level, buffer, buflen)
356 buffer[buflen+1] = "\n"
357 buffer[buflen+2] = strrep (" ", level)
358 buflen = buflen + 2
359 return buflen
360end
361
362function 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
367end
368
369local encode2 -- forward declaration
370
371local 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)
386end
387
388encode2 = 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
495end
496
497function 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
512end
513
514local 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)
527end
528
529local function unterminated (str, what, where)
530 return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
531end
532
533local 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
544end
545
546local escapechars = {
547 ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
548 ["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
549}
550
551local 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
571end
572
573local 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
632end
633
634local scanvalue -- forward declaration
635
636local 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
679end
680
681scanvalue = 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
715end
716
717local function optionalmetatables(...)
718 if select("#", ...) > 0 then
719 return ...
720 else
721 return {__jsontype = 'object'}, {__jsontype = 'array'}
722 end
723end
724
725function json.decode (str, pos, nullval, ...)
726 local objectmeta, arraymeta = optionalmetatables(...)
727 return scanvalue (str, pos, nullval, objectmeta, arraymeta)
728end
729
730function 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()
835end
836
837if always_try_using_lpeg then
838 pcall (json.use_lpeg)
839end
840
841return json
842
843-->