aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--spec/config_spec.lua16
-rw-r--r--spec/upload_spec.lua1
-rw-r--r--src/luarocks/cmd/config.lua6
-rw-r--r--src/luarocks/upload/api.lua5
-rw-r--r--src/luarocks/util.lua21
-rw-r--r--src/luarocks/vendor/dkjson.lua748
6 files changed, 750 insertions, 47 deletions
diff --git a/spec/config_spec.lua b/spec/config_spec.lua
index 885b022a..05da0007 100644
--- a/spec/config_spec.lua
+++ b/spec/config_spec.lua
@@ -23,17 +23,9 @@ describe("LuaRocks config tests #integration", function()
23 end) 23 end)
24 24
25 it("--json", function() 25 it("--json", function()
26 assert.is_true(run.luarocks_nocov("install dkjson"))
27 finally(function()
28 assert.is_true(run.luarocks_nocov("remove dkjson"))
29 end)
30 assert.match('"rocks_servers":[', run.luarocks("config --json"), 1, true) 26 assert.match('"rocks_servers":[', run.luarocks("config --json"), 1, true)
31 end) 27 end)
32 28
33 it("--json fails without a json library", function()
34 assert.falsy(run.luarocks_bool("config --json"))
35 end)
36
37 it("with --tree respects custom config", function() 29 it("with --tree respects custom config", function()
38 write_file("my_config.lua", [[ 30 write_file("my_config.lua", [[
39 rocks_trees = { 31 rocks_trees = {
@@ -167,18 +159,10 @@ describe("LuaRocks config tests #integration", function()
167 end) 159 end)
168 160
169 it("can read as JSON", function() 161 it("can read as JSON", function()
170 assert.is_true(run.luarocks_nocov("install dkjson"))
171 finally(function()
172 assert.is_true(run.luarocks_nocov("remove dkjson"))
173 end)
174 local output = run.luarocks("config rocks_trees --json") 162 local output = run.luarocks("config rocks_trees --json")
175 assert.match('^%["', output) 163 assert.match('^%["', output)
176 end) 164 end)
177 165
178 it("--json does not work without a json library", function()
179 assert.is_false(run.luarocks_bool("config rocks_trees --json"))
180 end)
181
182 it("reads an array -> hash config key", function() 166 it("reads an array -> hash config key", function()
183 local output = run.luarocks("config rocks_trees[2].name") 167 local output = run.luarocks("config rocks_trees[2].name")
184 assert.match("[a-z]+", output) 168 assert.match("[a-z]+", output)
diff --git a/spec/upload_spec.lua b/spec/upload_spec.lua
index 73f26cd7..119d34b6 100644
--- a/spec/upload_spec.lua
+++ b/spec/upload_spec.lua
@@ -27,7 +27,6 @@ describe("luarocks upload #integration", function()
27 end) 27 end)
28 28
29 it("force #unix", function() 29 it("force #unix", function()
30 assert.is_true(test_env.need_rock("dkjson"))
31 assert.is_false(run.luarocks_bool("upload --api-key=\"invalid\" --force " .. testing_paths.testing_server .. "/luasocket-${LUASOCKET}.rockspec")) 30 assert.is_false(run.luarocks_bool("upload --api-key=\"invalid\" --force " .. testing_paths.testing_server .. "/luasocket-${LUASOCKET}.rockspec"))
32 end) 31 end)
33 32
diff --git a/src/luarocks/cmd/config.lua b/src/luarocks/cmd/config.lua
index 7ad28777..3b100429 100644
--- a/src/luarocks/cmd/config.lua
+++ b/src/luarocks/cmd/config.lua
@@ -8,6 +8,7 @@ local util = require("luarocks.util")
8local deps = require("luarocks.deps") 8local deps = require("luarocks.deps")
9local dir = require("luarocks.dir") 9local dir = require("luarocks.dir")
10local fs = require("luarocks.fs") 10local fs = require("luarocks.fs")
11local json = require("luarocks.vendor.dkjson")
11 12
12function config_cmd.add_to_parser(parser) 13function config_cmd.add_to_parser(parser)
13 local cmd = parser:command("config", [[ 14 local cmd = parser:command("config", [[
@@ -135,11 +136,6 @@ local function traverse_varstring(var, tbl, fn, missing_parent)
135end 136end
136 137
137local function print_json(value) 138local function print_json(value)
138 local json_ok, json = util.require_json()
139 if not json_ok then
140 return nil, "A JSON library is required for this command. "..json
141 end
142
143 print(json.encode(value)) 139 print(json.encode(value))
144 return true 140 return true
145end 141end
diff --git a/src/luarocks/upload/api.lua b/src/luarocks/upload/api.lua
index cbfe6e9f..8e26f66a 100644
--- a/src/luarocks/upload/api.lua
+++ b/src/luarocks/upload/api.lua
@@ -7,6 +7,7 @@ local dir = require("luarocks.dir")
7local util = require("luarocks.util") 7local util = require("luarocks.util")
8local persist = require("luarocks.persist") 8local persist = require("luarocks.persist")
9local multipart = require("luarocks.upload.multipart") 9local multipart = require("luarocks.upload.multipart")
10local json = require("luarocks.vendor.dkjson")
10 11
11local Api = {} 12local Api = {}
12 13
@@ -118,8 +119,6 @@ if not ltn12_ok then -- If not using LuaSocket and/or LuaSec...
118 119
119function Api:request(url, params, post_params) 120function Api:request(url, params, post_params)
120 local vars = cfg.variables 121 local vars = cfg.variables
121 local json_ok, json = util.require_json()
122 if not json_ok then return nil, "A JSON library is required for this command. "..json end
123 122
124 if fs.which_tool("downloader") == "wget" then 123 if fs.which_tool("downloader") == "wget" then
125 local curl_ok, err = fs.is_tool_available(vars.CURL, "curl") 124 local curl_ok, err = fs.is_tool_available(vars.CURL, "curl")
@@ -182,8 +181,6 @@ else -- use LuaSocket and LuaSec
182local warned_luasec = false 181local warned_luasec = false
183 182
184function Api:request(url, params, post_params) 183function Api:request(url, params, post_params)
185 local json_ok, json = util.require_json()
186 if not json_ok then return nil, "A JSON library is required for this command. "..json end
187 local server = tostring(self.config.server) 184 local server = tostring(self.config.server)
188 local http_ok, http 185 local http_ok, http
189 local via = "luasocket" 186 local via = "luasocket"
diff --git a/src/luarocks/util.lua b/src/luarocks/util.lua
index 0a900ecc..902656fd 100644
--- a/src/luarocks/util.lua
+++ b/src/luarocks/util.lua
@@ -388,27 +388,6 @@ function util.deep_copy(tbl)
388 return copy 388 return copy
389end 389end
390 390
391-- An ode to the multitude of JSON libraries out there...
392function util.require_json()
393 local list = { "cjson", "dkjson", "json" }
394 for _, lib in ipairs(list) do
395 local json_ok, json = pcall(require, lib)
396 if json_ok then
397 pcall(json.use_lpeg) -- optional feature in dkjson
398 return json_ok, json
399 end
400 end
401 local errmsg = "Failed loading "
402 for i, name in ipairs(list) do
403 if i == #list then
404 errmsg = errmsg .."and '"..name.."'. Use 'luarocks search <partial-name>' to search for a library and 'luarocks install <name>' to install one."
405 else
406 errmsg = errmsg .."'"..name.."', "
407 end
408 end
409 return nil, errmsg
410end
411
412-- A portable version of fs.exists that can be used at early startup, 391-- A portable version of fs.exists that can be used at early startup,
413-- before the platform has been determined and luarocks.fs has been 392-- before the platform has been determined and luarocks.fs has been
414-- initialized. 393-- initialized.
diff --git a/src/luarocks/vendor/dkjson.lua b/src/luarocks/vendor/dkjson.lua
new file mode 100644
index 00000000..95e4d884
--- /dev/null
+++ b/src/luarocks/vendor/dkjson.lua
@@ -0,0 +1,748 @@
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.6
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-2021 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 =
46 pairs, type, tostring, tonumber, getmetatable, setmetatable
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.6" }
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-- blocking globals in Lua 5.2 and later
67local _ENV = nil -- luacheck: ignore 211
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 prev = true -- add a seperator before the next element
332 end
333 end
334 for k,v in pairs (value) do
335 if not used[k] then
336 buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
337 if not buflen then return nil, msg end
338 prev = true -- add a seperator before the next element
339 end
340 end
341 else -- unordered
342 for k,v in pairs (value) do
343 buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
344 if not buflen then return nil, msg end
345 prev = true -- add a seperator before the next element
346 end
347 end
348 if indent then
349 buflen = addnewline2 (level - 1, buffer, buflen)
350 end
351 buflen = buflen + 1
352 buffer[buflen] = "}"
353 end
354 tables[value] = nil
355 else
356 return exception ('unsupported type', value, state, buffer, buflen,
357 "type '" .. valtype .. "' is not supported by JSON.")
358 end
359 return buflen
360end
361
362function json.encode (value, state)
363 state = state or {}
364 local oldbuffer = state.buffer
365 local buffer = oldbuffer or {}
366 state.buffer = buffer
367 updatedecpoint()
368 local ret, msg = encode2 (value, state.indent, state.level or 0,
369 buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
370 if not ret then
371 error (msg, 2)
372 elseif oldbuffer == buffer then
373 state.bufferlen = ret
374 return true
375 else
376 state.bufferlen = nil
377 state.buffer = nil
378 return concat (buffer)
379 end
380end
381
382local function loc (str, where)
383 local line, pos, linepos = 1, 1, 0
384 while true do
385 pos = strfind (str, "\n", pos, true)
386 if pos and pos < where then
387 line = line + 1
388 linepos = pos
389 pos = pos + 1
390 else
391 break
392 end
393 end
394 return "line " .. line .. ", column " .. (where - linepos)
395end
396
397local function unterminated (str, what, where)
398 return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
399end
400
401local function scanwhite (str, pos)
402 while true do
403 pos = strfind (str, "%S", pos)
404 if not pos then return nil end
405 local sub2 = strsub (str, pos, pos + 1)
406 if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
407 -- UTF-8 Byte Order Mark
408 pos = pos + 3
409 elseif sub2 == "//" then
410 pos = strfind (str, "[\n\r]", pos + 2)
411 if not pos then return nil end
412 elseif sub2 == "/*" then
413 pos = strfind (str, "*/", pos + 2)
414 if not pos then return nil end
415 pos = pos + 2
416 else
417 return pos
418 end
419 end
420end
421
422local escapechars = {
423 ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
424 ["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
425}
426
427local function unichar (value)
428 if value < 0 then
429 return nil
430 elseif value <= 0x007f then
431 return strchar (value)
432 elseif value <= 0x07ff then
433 return strchar (0xc0 + floor(value/0x40),
434 0x80 + (floor(value) % 0x40))
435 elseif value <= 0xffff then
436 return strchar (0xe0 + floor(value/0x1000),
437 0x80 + (floor(value/0x40) % 0x40),
438 0x80 + (floor(value) % 0x40))
439 elseif value <= 0x10ffff then
440 return strchar (0xf0 + floor(value/0x40000),
441 0x80 + (floor(value/0x1000) % 0x40),
442 0x80 + (floor(value/0x40) % 0x40),
443 0x80 + (floor(value) % 0x40))
444 else
445 return nil
446 end
447end
448
449local function scanstring (str, pos)
450 local lastpos = pos + 1
451 local buffer, n = {}, 0
452 while true do
453 local nextpos = strfind (str, "[\"\\]", lastpos)
454 if not nextpos then
455 return unterminated (str, "string", pos)
456 end
457 if nextpos > lastpos then
458 n = n + 1
459 buffer[n] = strsub (str, lastpos, nextpos - 1)
460 end
461 if strsub (str, nextpos, nextpos) == "\"" then
462 lastpos = nextpos + 1
463 break
464 else
465 local escchar = strsub (str, nextpos + 1, nextpos + 1)
466 local value
467 if escchar == "u" then
468 value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
469 if value then
470 local value2
471 if 0xD800 <= value and value <= 0xDBff then
472 -- we have the high surrogate of UTF-16. Check if there is a
473 -- low surrogate escaped nearby to combine them.
474 if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
475 value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
476 if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
477 value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
478 else
479 value2 = nil -- in case it was out of range for a low surrogate
480 end
481 end
482 end
483 value = value and unichar (value)
484 if value then
485 if value2 then
486 lastpos = nextpos + 12
487 else
488 lastpos = nextpos + 6
489 end
490 end
491 end
492 end
493 if not value then
494 value = escapechars[escchar] or escchar
495 lastpos = nextpos + 2
496 end
497 n = n + 1
498 buffer[n] = value
499 end
500 end
501 if n == 1 then
502 return buffer[1], lastpos
503 elseif n > 1 then
504 return concat (buffer), lastpos
505 else
506 return "", lastpos
507 end
508end
509
510local scanvalue -- forward declaration
511
512local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
513
514 local tbl, n = {}, 0
515 local pos = startpos + 1
516 if what == 'object' then
517 setmetatable (tbl, objectmeta)
518 else
519 setmetatable (tbl, arraymeta)
520 end
521 while true do
522 pos = scanwhite (str, pos)
523 if not pos then return unterminated (str, what, startpos) end
524 local char = strsub (str, pos, pos)
525 if char == closechar then
526 return tbl, pos + 1
527 end
528 local val1, err
529 val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
530 if err then return nil, pos, err end
531 pos = scanwhite (str, pos)
532 if not pos then return unterminated (str, what, startpos) end
533 char = strsub (str, pos, pos)
534 if char == ":" then
535 if val1 == nil then
536 return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
537 end
538 pos = scanwhite (str, pos + 1)
539 if not pos then return unterminated (str, what, startpos) end
540 local val2
541 val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
542 if err then return nil, pos, err end
543 tbl[val1] = val2
544 pos = scanwhite (str, pos)
545 if not pos then return unterminated (str, what, startpos) end
546 char = strsub (str, pos, pos)
547 else
548 n = n + 1
549 tbl[n] = val1
550 end
551 if char == "," then
552 pos = pos + 1
553 end
554 end
555end
556
557scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
558 pos = pos or 1
559 pos = scanwhite (str, pos)
560 if not pos then
561 return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
562 end
563 local char = strsub (str, pos, pos)
564 if char == "{" then
565 return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
566 elseif char == "[" then
567 return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
568 elseif char == "\"" then
569 return scanstring (str, pos)
570 else
571 local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
572 if pstart then
573 local number = str2num (strsub (str, pstart, pend))
574 if number then
575 return number, pend + 1
576 end
577 end
578 pstart, pend = strfind (str, "^%a%w*", pos)
579 if pstart then
580 local name = strsub (str, pstart, pend)
581 if name == "true" then
582 return true, pend + 1
583 elseif name == "false" then
584 return false, pend + 1
585 elseif name == "null" then
586 return nullval, pend + 1
587 end
588 end
589 return nil, pos, "no valid JSON value at " .. loc (str, pos)
590 end
591end
592
593local function optionalmetatables(...)
594 if select("#", ...) > 0 then
595 return ...
596 else
597 return {__jsontype = 'object'}, {__jsontype = 'array'}
598 end
599end
600
601function json.decode (str, pos, nullval, ...)
602 local objectmeta, arraymeta = optionalmetatables(...)
603 return scanvalue (str, pos, nullval, objectmeta, arraymeta)
604end
605
606function json.use_lpeg ()
607 local g = require ("lpeg")
608
609 if g.version() == "0.11" then
610 error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
611 end
612
613 local pegmatch = g.match
614 local P, S, R = g.P, g.S, g.R
615
616 local function ErrorCall (str, pos, msg, state)
617 if not state.msg then
618 state.msg = msg .. " at " .. loc (str, pos)
619 state.pos = pos
620 end
621 return false
622 end
623
624 local function Err (msg)
625 return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
626 end
627
628 local function ErrorUnterminatedCall (str, pos, what, state)
629 return ErrorCall (str, pos - 1, "unterminated " .. what, state)
630 end
631
632 local SingleLineComment = P"//" * (1 - S"\n\r")^0
633 local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
634 local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
635
636 local function ErrUnterminated (what)
637 return g.Cmt (g.Cc (what) * g.Carg (2), ErrorUnterminatedCall)
638 end
639
640 local PlainChar = 1 - S"\"\\\n\r"
641 local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
642 local HexDigit = R("09", "af", "AF")
643 local function UTF16Surrogate (match, pos, high, low)
644 high, low = tonumber (high, 16), tonumber (low, 16)
645 if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
646 return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
647 else
648 return false
649 end
650 end
651 local function UTF16BMP (hex)
652 return unichar (tonumber (hex, 16))
653 end
654 local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
655 local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
656 local Char = UnicodeEscape + EscapeSequence + PlainChar
657 local String = P"\"" * (g.Cs (Char ^ 0) * P"\"" + ErrUnterminated "string")
658 local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
659 local Fractal = P"." * R"09"^0
660 local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
661 local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
662 local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
663 local SimpleValue = Number + String + Constant
664 local ArrayContent, ObjectContent
665
666 -- The functions parsearray and parseobject parse only a single value/pair
667 -- at a time and store them directly to avoid hitting the LPeg limits.
668 local function parsearray (str, pos, nullval, state)
669 local obj, cont
670 local start = pos
671 local npos
672 local t, nt = {}, 0
673 repeat
674 obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
675 if cont == 'end' then
676 return ErrorUnterminatedCall (str, start, "array", state)
677 end
678 pos = npos
679 if cont == 'cont' or cont == 'last' then
680 nt = nt + 1
681 t[nt] = obj
682 end
683 until cont ~= 'cont'
684 return pos, setmetatable (t, state.arraymeta)
685 end
686
687 local function parseobject (str, pos, nullval, state)
688 local obj, key, cont
689 local start = pos
690 local npos
691 local t = {}
692 repeat
693 key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
694 if cont == 'end' then
695 return ErrorUnterminatedCall (str, start, "object", state)
696 end
697 pos = npos
698 if cont == 'cont' or cont == 'last' then
699 t[key] = obj
700 end
701 until cont ~= 'cont'
702 return pos, setmetatable (t, state.objectmeta)
703 end
704
705 local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray)
706 local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject)
707 local Value = Space * (Array + Object + SimpleValue)
708 local ExpectedValue = Value + Space * Err "value expected"
709 local ExpectedKey = String + Err "key expected"
710 local End = P(-1) * g.Cc'end'
711 local ErrInvalid = Err "invalid JSON"
712 ArrayContent = (Value * Space * (P"," * g.Cc'cont' + P"]" * g.Cc'last'+ End + ErrInvalid) + g.Cc(nil) * (P"]" * g.Cc'empty' + End + ErrInvalid)) * g.Cp()
713 local Pair = g.Cg (Space * ExpectedKey * Space * (P":" + Err "colon expected") * ExpectedValue)
714 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()
715 local DecodeValue = ExpectedValue * g.Cp ()
716
717 jsonlpeg.version = json.version
718 jsonlpeg.encode = json.encode
719 jsonlpeg.null = json.null
720 jsonlpeg.quotestring = json.quotestring
721 jsonlpeg.addnewline = json.addnewline
722 jsonlpeg.encodeexception = json.encodeexception
723 jsonlpeg.using_lpeg = true
724
725 function jsonlpeg.decode (str, pos, nullval, ...)
726 local state = {}
727 state.objectmeta, state.arraymeta = optionalmetatables(...)
728 local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
729 if state.msg then
730 return nil, state.pos, state.msg
731 else
732 return obj, retpos
733 end
734 end
735
736 -- cache result of this function:
737 json.use_lpeg = function () return jsonlpeg end
738 jsonlpeg.use_lpeg = json.use_lpeg
739
740 return jsonlpeg
741end
742
743if always_use_lpeg then
744 return json.use_lpeg()
745end
746
747return json
748