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 /src | |
| 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.
Diffstat (limited to 'src')
| -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 |
4 files changed, 750 insertions, 30 deletions
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 | |||
