diff options
author | Hisham Muhammad <hisham@gobolinux.org> | 2024-02-18 21:06:10 -0300 |
---|---|---|
committer | Hisham Muhammad <hisham@gobolinux.org> | 2024-02-19 08:23:41 -0300 |
commit | 21b08e29ec1d4aa887d61a27b8fa8a75232522af (patch) | |
tree | 94510019d8b3064bc700c5d8f2307e0ed712897c | |
parent | fe4cb07deb73bd9c06059d266f59f5a3a85e05c7 (diff) | |
download | luarocks-21b08e29ec1d4aa887d61a27b8fa8a75232522af.tar.gz luarocks-21b08e29ec1d4aa887d61a27b8fa8a75232522af.tar.bz2 luarocks-21b08e29ec1d4aa887d61a27b8fa8a75232522af.zip |
vendor in the dkjson dependency
Fixes #1243.
Fixes #1168.
Fixes #559.
-rw-r--r-- | spec/config_spec.lua | 16 | ||||
-rw-r--r-- | spec/upload_spec.lua | 1 | ||||
-rw-r--r-- | src/luarocks/cmd/config.lua | 6 | ||||
-rw-r--r-- | src/luarocks/upload/api.lua | 5 | ||||
-rw-r--r-- | src/luarocks/util.lua | 21 | ||||
-rw-r--r-- | src/luarocks/vendor/dkjson.lua | 748 |
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") | |||
8 | local deps = require("luarocks.deps") | 8 | local deps = require("luarocks.deps") |
9 | local dir = require("luarocks.dir") | 9 | local dir = require("luarocks.dir") |
10 | local fs = require("luarocks.fs") | 10 | local fs = require("luarocks.fs") |
11 | local json = require("luarocks.vendor.dkjson") | ||
11 | 12 | ||
12 | function config_cmd.add_to_parser(parser) | 13 | function 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) | |||
135 | end | 136 | end |
136 | 137 | ||
137 | local function print_json(value) | 138 | local 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 |
145 | end | 141 | end |
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") | |||
7 | local util = require("luarocks.util") | 7 | local util = require("luarocks.util") |
8 | local persist = require("luarocks.persist") | 8 | local persist = require("luarocks.persist") |
9 | local multipart = require("luarocks.upload.multipart") | 9 | local multipart = require("luarocks.upload.multipart") |
10 | local json = require("luarocks.vendor.dkjson") | ||
10 | 11 | ||
11 | local Api = {} | 12 | local Api = {} |
12 | 13 | ||
@@ -118,8 +119,6 @@ if not ltn12_ok then -- If not using LuaSocket and/or LuaSec... | |||
118 | 119 | ||
119 | function Api:request(url, params, post_params) | 120 | function 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 | |||
182 | local warned_luasec = false | 181 | local warned_luasec = false |
183 | 182 | ||
184 | function Api:request(url, params, post_params) | 183 | function 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 |
389 | end | 389 | end |
390 | 390 | ||
391 | -- An ode to the multitude of JSON libraries out there... | ||
392 | function 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 | ||
410 | end | ||
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: | ||
2 | local always_use_lpeg = false | ||
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.4 | ||
9 | |||
10 | Version 2.6 | ||
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-2021 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 = | ||
46 | pairs, type, tostring, tonumber, getmetatable, setmetatable | ||
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.6" } | ||
56 | |||
57 | local jsonlpeg = {} | ||
58 | |||
59 | if 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 | ||
65 | end | ||
66 | -- blocking globals in Lua 5.2 and later | ||
67 | local _ENV = nil -- luacheck: ignore 211 | ||
68 | |||
69 | pcall (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 | ||
74 | end) | ||
75 | |||
76 | json.null = setmetatable ({}, { | ||
77 | __tojson = function () return "null" end | ||
78 | }) | ||
79 | |||
80 | local 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 | ||
102 | end | ||
103 | |||
104 | local escapecodes = { | ||
105 | ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", | ||
106 | ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t" | ||
107 | } | ||
108 | |||
109 | local 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 | ||
137 | end | ||
138 | |||
139 | local 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 | ||
148 | end | ||
149 | |||
150 | local 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 .. "\"" | ||
164 | end | ||
165 | json.quotestring = quotestring | ||
166 | |||
167 | local 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 | ||
174 | end | ||
175 | |||
176 | -- locale independent num2str and str2num functions | ||
177 | local decpoint, numfilter | ||
178 | |||
179 | local 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") .. "]+" | ||
183 | end | ||
184 | |||
185 | updatedecpoint() | ||
186 | |||
187 | local function num2str (num) | ||
188 | return replace(fsub(tostring(num), numfilter, ""), decpoint, ".") | ||
189 | end | ||
190 | |||
191 | local 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 | ||
198 | end | ||
199 | |||
200 | local function addnewline2 (level, buffer, buflen) | ||
201 | buffer[buflen+1] = "\n" | ||
202 | buffer[buflen+2] = strrep (" ", level) | ||
203 | buflen = buflen + 2 | ||
204 | return buflen | ||
205 | end | ||
206 | |||
207 | function 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 | ||
212 | end | ||
213 | |||
214 | local encode2 -- forward declaration | ||
215 | |||
216 | local 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) | ||
231 | end | ||
232 | |||
233 | local 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 | ||
240 | end | ||
241 | |||
242 | local 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 | ||
253 | end | ||
254 | |||
255 | function json.encodeexception(reason, value, state, defaultmessage) | ||
256 | return quotestring("<" .. defaultmessage .. ">") | ||
257 | end | ||
258 | |||
259 | encode2 = 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 | ||
360 | end | ||
361 | |||
362 | function 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 | ||
380 | end | ||
381 | |||
382 | local 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) | ||
395 | end | ||
396 | |||
397 | local function unterminated (str, what, where) | ||
398 | return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where) | ||
399 | end | ||
400 | |||
401 | local 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 | ||
420 | end | ||
421 | |||
422 | local escapechars = { | ||
423 | ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f", | ||
424 | ["n"] = "\n", ["r"] = "\r", ["t"] = "\t" | ||
425 | } | ||
426 | |||
427 | local 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 | ||
447 | end | ||
448 | |||
449 | local 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 | ||
508 | end | ||
509 | |||
510 | local scanvalue -- forward declaration | ||
511 | |||
512 | local 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 | ||
555 | end | ||
556 | |||
557 | scanvalue = 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 | ||
591 | end | ||
592 | |||
593 | local function optionalmetatables(...) | ||
594 | if select("#", ...) > 0 then | ||
595 | return ... | ||
596 | else | ||
597 | return {__jsontype = 'object'}, {__jsontype = 'array'} | ||
598 | end | ||
599 | end | ||
600 | |||
601 | function json.decode (str, pos, nullval, ...) | ||
602 | local objectmeta, arraymeta = optionalmetatables(...) | ||
603 | return scanvalue (str, pos, nullval, objectmeta, arraymeta) | ||
604 | end | ||
605 | |||
606 | function 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 | ||
741 | end | ||
742 | |||
743 | if always_use_lpeg then | ||
744 | return json.use_lpeg() | ||
745 | end | ||
746 | |||
747 | return json | ||
748 | |||