- performance increase;
pull/48/head
DarhangeR 2021-11-27 14:13:53 +02:00 committed by GitHub
parent dbf4b2dd2e
commit 7eaffd1234
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 374 additions and 388 deletions

762
json.lua
View File

@ -1,388 +1,374 @@
-- local json = { _version = "0.1.3" }
-- json.lua
-- -------------------------------------------------------------------------------
-- Copyright (c) 2020 rxi -- Encode
-- -------------------------------------------------------------------------------
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in local encode
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies local escape_char_map = {
-- of the Software, and to permit persons to whom the Software is furnished to do [ "\\" ] = "\\",
-- so, subject to the following conditions: [ "\"" ] = "\"",
-- [ "\b" ] = "b",
-- The above copyright notice and this permission notice shall be included in all [ "\f" ] = "f",
-- copies or substantial portions of the Software. [ "\n" ] = "n",
-- [ "\r" ] = "r",
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR [ "\t" ] = "t",
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, }
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER local escape_char_map_inv = { [ "/" ] = "/" }
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, for k, v in pairs(escape_char_map) do
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE escape_char_map_inv[v] = k
-- SOFTWARE. end
--
local json = { _version = "0.1.2" } local function escape_char(c)
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
------------------------------------------------------------------------------- end
-- Encode
-------------------------------------------------------------------------------
local function encode_nil(val)
local encode return "null"
end
local escape_char_map = {
[ "\\" ] = "\\",
[ "\"" ] = "\"", local function encode_table(val, stack)
[ "\b" ] = "b", local res = {}
[ "\f" ] = "f", stack = stack or {}
[ "\n" ] = "n",
[ "\r" ] = "r", -- Circular reference?
[ "\t" ] = "t", if stack[val] then error("circular reference") end
}
stack[val] = true
local escape_char_map_inv = { [ "/" ] = "/" } -- Check whether to treat as a array or object
for k, v in pairs(escape_char_map) do local array = true
escape_char_map_inv[v] = k local length = 0
end local nLen = 0
for k,v in pairs(val) do
if (type(k) ~= "number" or k<=0) and not (k == "n" and type(v) == "number") then
local function escape_char(c) array = nil
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) break -- Treat as object
end else
if k > length then
length = k
local function encode_nil(val) end
return "null" if k == "n" and type(v) == "number" then
end nLen = v
end
end
local function encode_table(val, stack) end
local res = {}
stack = stack or {} if array then
if nLen > length then
-- Circular reference? length = nLen
if stack[val] then error("circular reference") end end
-- Encode
stack[val] = true for i = 1, length do
table.insert(res, encode(val[i], stack))
if rawget(val, 1) ~= nil or next(val) == nil then end
-- Treat as array -- check keys are valid and it is not sparse stack[val] = nil
local n = 0 return "[" .. table.concat(res, ",") .. "]"
for k in pairs(val) do
if type(k) ~= "number" then else
error("invalid table: mixed or invalid key types") -- Treat as an object
end for k, v in pairs(val) do
n = n + 1 --[[
end if type(k) ~= "string" then
if n ~= #val then error("invalid table: mixed or invalid key types")
error("invalid table: sparse array") end
end ]]
-- Encode table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
for i, v in ipairs(val) do end
table.insert(res, encode(v, stack)) stack[val] = nil
end return "{" .. table.concat(res, ",") .. "}"
stack[val] = nil end
return "[" .. table.concat(res, ",") .. "]" end
else
-- Treat as an object local function encode_string(val)
for k, v in pairs(val) do return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
if type(k) ~= "string" then end
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) local function encode_number(val)
end -- Check for NaN, -inf and inf
stack[val] = nil if val ~= val or val <= -math.huge or val >= math.huge then
return "{" .. table.concat(res, ",") .. "}" error("unexpected number value '" .. tostring(val) .. "'")
end end
end return tostring(val)
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' local type_func_map = {
end [ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
local function encode_number(val) [ "number" ] = encode_number,
-- Check for NaN, -inf and inf [ "boolean" ] = tostring,
if val ~= val or val <= -math.huge or val >= math.huge then }
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val) encode = function(val, stack)
end local t = type(val)
local f = type_func_map[t]
if f then
local type_func_map = { return f(val, stack)
[ "nil" ] = encode_nil, end
[ "table" ] = encode_table, error("unexpected type '" .. t .. "'")
[ "string" ] = encode_string, end
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
} function json.encode(val)
return ( encode(val) )
end
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t] -------------------------------------------------------------------------------
if f then -- Decode
return f(val, stack) -------------------------------------------------------------------------------
end
error("unexpected type '" .. t .. "'") local parse
end
local function create_set(...)
local res = {}
function json.encode(val) for i = 1, select("#", ...) do
return ( encode(val) ) res[ select(i, ...) ] = true
end end
return res
end
-------------------------------------------------------------------------------
-- Decode local space_chars = create_set(" ", "\t", "\r", "\n")
------------------------------------------------------------------------------- local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local parse local literals = create_set("true", "false", "null")
local function create_set(...) local literal_map = {
local res = {} [ "true" ] = true,
for i = 1, select("#", ...) do [ "false" ] = false,
res[ select(i, ...) ] = true [ "null" ] = nil,
end }
return res
end
local function next_char(str, idx, set, negate)
local space_chars = create_set(" ", "\t", "\r", "\n") for i = idx, #str do
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") if set[str:sub(i, i)] ~= negate then
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") return i
local literals = create_set("true", "false", "null") end
end
local literal_map = { return #str + 1
[ "true" ] = true, end
[ "false" ] = false,
[ "null" ] = nil,
} local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
local function next_char(str, idx, set, negate) for i = 1, idx - 1 do
for i = idx, #str do col_count = col_count + 1
if set[str:sub(i, i)] ~= negate then if str:sub(i, i) == "\n" then
return i line_count = line_count + 1
end col_count = 1
end end
return #str + 1 end
end error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function decode_error(str, idx, msg)
local line_count = 1 local function codepoint_to_utf8(n)
local col_count = 1 -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
for i = 1, idx - 1 do local f = math.floor
col_count = col_count + 1 if n <= 0x7f then
if str:sub(i, i) == "\n" then return string.char(n)
line_count = line_count + 1 elseif n <= 0x7ff then
col_count = 1 return string.char(f(n / 64) + 192, n % 64 + 128)
end elseif n <= 0xffff then
end return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
error( string.format("%s at line %d col %d", msg, line_count, col_count) ) elseif n <= 0x10ffff then
end return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
local function codepoint_to_utf8(n) error( string.format("invalid unicode codepoint '%x'", n) )
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa end
local f = math.floor
if n <= 0x7f then
return string.char(n) local function parse_unicode_escape(s)
elseif n <= 0x7ff then local n1 = tonumber( s:sub(1, 4), 16 )
return string.char(f(n / 64) + 192, n % 64 + 128) local n2 = tonumber( s:sub(7, 10), 16 )
elseif n <= 0xffff then -- Surrogate pair?
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) if n2 then
elseif n <= 0x10ffff then return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, else
f(n % 4096 / 64) + 128, n % 64 + 128) return codepoint_to_utf8(n1)
end end
error( string.format("invalid unicode codepoint '%x'", n) ) end
end
local function parse_string(str, i)
local function parse_unicode_escape(s) local res = {}
local n1 = tonumber( s:sub(1, 4), 16 ) local j = i + 1
local n2 = tonumber( s:sub(7, 10), 16 ) local k = j
-- Surrogate pair?
if n2 then while j <= #str do
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) local x = str:byte(j)
else
return codepoint_to_utf8(n1) if x < 32 then
end decode_error(str, j, "control character in string")
end
elseif x == 92 then -- `\`: Escape
table.insert(res, str:sub(k, j - 1))
local function parse_string(str, i) j = j + 1
local res = "" local c = str:sub(j, j)
local j = i + 1 if c == "u" then
local k = j local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
or str:match("^%x%x%x%x", j + 1)
while j <= #str do or decode_error(str, j - 1, "invalid unicode escape in string")
local x = str:byte(j) table.insert(res, parse_unicode_escape(hex))
j = j + #hex
if x < 32 then else
decode_error(str, j, "control character in string") if not escape_chars[c] then
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
elseif x == 92 then -- `\`: Escape end
res = res .. str:sub(k, j - 1) table.insert(res, escape_char_map_inv[c])
j = j + 1 end
local c = str:sub(j, j) k = j + 1
if c == "u" then
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) elseif x == 34 then -- `"`: End of string
or str:match("^%x%x%x%x", j + 1) table.insert(res, str:sub(k, j - 1))
or decode_error(str, j - 1, "invalid unicode escape in string") return table.concat(res), j + 1
res = res .. parse_unicode_escape(hex) end
j = j + #hex
else j = j + 1
if not escape_chars[c] then end
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
end decode_error(str, i, "expected closing quote for string")
res = res .. escape_char_map_inv[c] end
end
k = j + 1
local function parse_number(str, i)
elseif x == 34 then -- `"`: End of string local x = next_char(str, i, delim_chars)
res = res .. str:sub(k, j - 1) local s = str:sub(i, x - 1)
return res, j + 1 local n = tonumber(s)
end if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
j = j + 1 end
end return n, x
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local function parse_number(str, i) local word = str:sub(i, x - 1)
local x = next_char(str, i, delim_chars) if not literals[word] then
local s = str:sub(i, x - 1) decode_error(str, i, "invalid literal '" .. word .. "'")
local n = tonumber(s) end
if not n then return literal_map[word], x
decode_error(str, i, "invalid number '" .. s .. "'") end
end
return n, x
end local function parse_array(str, i)
local res = {}
local n = 1
local function parse_literal(str, i) i = i + 1
local x = next_char(str, i, delim_chars) while 1 do
local word = str:sub(i, x - 1) local x
if not literals[word] then i = next_char(str, i, space_chars, true)
decode_error(str, i, "invalid literal '" .. word .. "'") -- Empty / end of array?
end if str:sub(i, i) == "]" then
return literal_map[word], x i = i + 1
end break
end
-- Read token
local function parse_array(str, i) x, i = parse(str, i)
local res = {} res[n] = x
local n = 1 n = n + 1
i = i + 1 -- Next token
while 1 do i = next_char(str, i, space_chars, true)
local x local chr = str:sub(i, i)
i = next_char(str, i, space_chars, true) i = i + 1
-- Empty / end of array? if chr == "]" then break end
if str:sub(i, i) == "]" then if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
i = i + 1 end
break return res, i
end end
-- Read token
x, i = parse(str, i)
res[n] = x local function parse_object(str, i)
n = n + 1 local res = {}
-- Next token i = i + 1
i = next_char(str, i, space_chars, true) while 1 do
local chr = str:sub(i, i) local key, val
i = i + 1 i = next_char(str, i, space_chars, true)
if chr == "]" then break end -- Empty / end of object?
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end if str:sub(i, i) == "}" then
end i = i + 1
return res, i break
end end
-- Read key
if str:sub(i, i) ~= '"' then
local function parse_object(str, i) decode_error(str, i, "expected string for key")
local res = {} end
i = i + 1 key, i = parse(str, i)
while 1 do -- Read ':' delimiter
local key, val i = next_char(str, i, space_chars, true)
i = next_char(str, i, space_chars, true) if str:sub(i, i) ~= ":" then
-- Empty / end of object? decode_error(str, i, "expected ':' after key")
if str:sub(i, i) == "}" then end
i = i + 1 i = next_char(str, i + 1, space_chars, true)
break -- Read value
end val, i = parse(str, i)
-- Read key -- Set
if str:sub(i, i) ~= '"' then res[key] = val
decode_error(str, i, "expected string for key") -- Next token
end i = next_char(str, i, space_chars, true)
key, i = parse(str, i) local chr = str:sub(i, i)
-- Read ':' delimiter i = i + 1
i = next_char(str, i, space_chars, true) if chr == "}" then break end
if str:sub(i, i) ~= ":" then if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
decode_error(str, i, "expected ':' after key") end
end return res, i
i = next_char(str, i + 1, space_chars, true) end
-- Read value
val, i = parse(str, i)
-- Set local char_func_map = {
res[key] = val [ '"' ] = parse_string,
-- Next token [ "0" ] = parse_number,
i = next_char(str, i, space_chars, true) [ "1" ] = parse_number,
local chr = str:sub(i, i) [ "2" ] = parse_number,
i = i + 1 [ "3" ] = parse_number,
if chr == "}" then break end [ "4" ] = parse_number,
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end [ "5" ] = parse_number,
end [ "6" ] = parse_number,
return res, i [ "7" ] = parse_number,
end [ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
local char_func_map = { [ "t" ] = parse_literal,
[ '"' ] = parse_string, [ "f" ] = parse_literal,
[ "0" ] = parse_number, [ "n" ] = parse_literal,
[ "1" ] = parse_number, [ "[" ] = parse_array,
[ "2" ] = parse_number, [ "{" ] = parse_object,
[ "3" ] = parse_number, }
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number, parse = function(str, idx)
[ "7" ] = parse_number, local chr = str:sub(idx, idx)
[ "8" ] = parse_number, local f = char_func_map[chr]
[ "9" ] = parse_number, if f then
[ "-" ] = parse_number, return f(str, idx)
[ "t" ] = parse_literal, end
[ "f" ] = parse_literal, decode_error(str, idx, "unexpected character '" .. chr .. "'")
[ "n" ] = parse_literal, end
[ "[" ] = parse_array,
[ "{" ] = parse_object,
} function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
parse = function(str, idx) end
local chr = str:sub(idx, idx) local res, idx = parse(str, next_char(str, 1, space_chars, true))
local f = char_func_map[chr] idx = next_char(str, idx, space_chars, true)
if f then if idx <= #str then
return f(str, idx) decode_error(str, idx, "trailing garbage")
end end
decode_error(str, idx, "unexpected character '" .. chr .. "'") return res
end end
return json;
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json