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