pull/46/merge
Lars Müller 2022-12-17 17:06:58 +01:00 committed by GitHub
commit 3a962afc66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 40 deletions

View File

@ -63,10 +63,10 @@ for i, name in ipairs(libs) do
end end
-- Warmup (for LuaJIT) -- Warmup (for LuaJIT)
bench.run(name, 1, function() json.decode(text) end) bench.run(name, 10, function() json.decode(text) end)
-- Run and push results -- Run and push results
local res = bench.run(name, 10, function() json.decode(text) end) local res = bench.run(name, 100, function() json.decode(text) end)
table.insert(results, res) table.insert(results, res)
end end

View File

@ -51,10 +51,10 @@ for i, name in ipairs(libs) do
end end
-- Warmup (for LuaJIT) -- Warmup (for LuaJIT)
bench.run(name, 1, function() json.encode(data) end) bench.run(name, 10, function() json.encode(data) end)
-- Run and push results -- Run and push results
local res = bench.run(name, 10, function() json.encode(data) end) local res = bench.run(name, 100, function() json.encode(data) end)
table.insert(results, res) table.insert(results, res)
end end

View File

@ -5,6 +5,10 @@ local fmt = string.format
function bench.run(name, count, func) function bench.run(name, count, func)
-- Ensure all garbage is collected
for _ = 1, 10 do -- cycles
collectgarbage()
end
-- Run bench -- Run bench
local res = {} local res = {}
for i = 1, count do for i = 1, count do

View File

@ -51,20 +51,16 @@ local function escape_char(c)
end end
local function encode_nil(val) local function encode_nil(rope)
return "null" rope[#rope + 1] = "null"
end end
local function encode_table(val, stack) local function encode_table(rope, val, stack)
local res = {}
stack = stack or {}
-- Circular reference? -- Circular reference?
if stack[val] then error("circular reference") end if stack[val] then error("circular reference") end
stack[val] = true stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse -- Treat as array -- check keys are valid and it is not sparse
local n = 0 local n = 0
@ -78,61 +74,80 @@ local function encode_table(val, stack)
error("invalid table: sparse array") error("invalid table: sparse array")
end end
-- Encode -- Encode
rope[#rope + 1] = "["
for i, v in ipairs(val) do for i, v in ipairs(val) do
table.insert(res, encode(v, stack)) if i > 1 then
rope[#rope + 1] = ","
end
encode(rope, v, stack)
end end
stack[val] = nil rope[#rope + 1] = "]"
return "[" .. table.concat(res, ",") .. "]"
else else
-- Treat as an object -- Treat as an object
rope[#rope + 1] = "{"
local first = true
for k, v in pairs(val) do for k, v in pairs(val) do
if type(k) ~= "string" then if type(k) ~= "string" then
error("invalid table: mixed or invalid key types") error("invalid table: mixed or invalid key types")
end end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) if not first then
rope[#rope + 1] = ","
end
encode(rope, k, stack)
rope[#rope + 1] = ":"
encode(rope, v, stack)
first = false
end end
stack[val] = nil rope[#rope + 1] = "}"
return "{" .. table.concat(res, ",") .. "}"
end end
stack[val] = nil
end end
local function encode_string(val) local function encode_string(rope, val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' rope[#rope + 1] = '"'
rope[#rope + 1] = val:gsub('[%z\1-\31\\"]', escape_char)
rope[#rope + 1] = '"'
end end
local function encode_number(val) local function encode_number(rope, val)
-- Check for NaN, -inf and inf -- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'") error("unexpected number value '" .. tostring(val) .. "'")
end end
return string.format("%.14g", val) -- See www.cs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF
-- 17 digits suffice to losslessly represent 64-bit IEEE754 floats
rope[#rope + 1] = ("%.17g"):format(val)
end end
local function encode_boolean(rope, val)
rope[#rope + 1] = val and "true" or "false"
end
local type_func_map = { local type_func_map = {
[ "nil" ] = encode_nil, [ "nil" ] = encode_nil,
[ "table" ] = encode_table, [ "table" ] = encode_table,
[ "string" ] = encode_string, [ "string" ] = encode_string,
[ "number" ] = encode_number, [ "number" ] = encode_number,
[ "boolean" ] = tostring, [ "boolean" ] = encode_boolean,
} }
encode = function(val, stack) function encode(rope, val, stack)
local t = type(val) local t = type(val)
local f = type_func_map[t] local encoder = type_func_map[t]
if f then if encoder then
return f(val, stack) return encoder(rope, val, stack)
end end
error("unexpected type '" .. t .. "'") error("unexpected type '" .. t .. "'")
end end
function json.encode(val) function json.encode(val)
return ( encode(val) ) local rope = {}
encode(rope, val, {})
return table.concat(rope)
end end
@ -216,7 +231,7 @@ end
local function parse_string(str, i) local function parse_string(str, i)
local res = "" local res = {}
local j = i + 1 local j = i + 1
local k = j local k = j
@ -227,32 +242,33 @@ local function parse_string(str, i)
decode_error(str, j, "control character in string") decode_error(str, j, "control character in string")
elseif x == 92 then -- `\`: Escape elseif x == 92 then -- `\`: Escape
res = res .. str:sub(k, j - 1) res[#res + 1] = str:sub(k, j - 1)
j = j + 1 j = j + 1
local c = str:sub(j, j) local c = str:sub(j, j)
if c == "u" then if c == "u" then
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
or str:match("^%x%x%x%x", j + 1) or str:match("^%x%x%x%x", j + 1)
or decode_error(str, j - 1, "invalid unicode escape in string") or decode_error(str, j - 1, "invalid unicode escape in string")
res = res .. parse_unicode_escape(hex) res[#res + 1] = parse_unicode_escape(hex)
j = j + #hex j = j + #hex
else else
if not escape_chars[c] then if not escape_chars[c] then
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
end end
res = res .. escape_char_map_inv[c] res[#res + 1] = escape_char_map_inv[c]
end end
k = j + 1 k = j + 1
elseif x == 34 then -- `"`: End of string elseif x == 34 then -- `"`: End of string
res = res .. str:sub(k, j - 1) res[#res + 1] = str:sub(k, j - 1)
return res, j + 1 return table.concat(res), j + 1
end end
j = j + 1 j = j + 1
end end
decode_error(str, i, "expected closing quote for string") decode_error(str, i, "expected closing quote for string")
return table.concat(res)
end end

View File

@ -36,12 +36,12 @@ end
test("numbers", function() test("numbers", function()
local t = { local t = {
[ "123.456" ] = 123.456, [ "123.456" ] = 123.456,
[ "-123" ] = -123, [ "-123" ] = -123,
[ "-567.765" ] = -567.765, [ "-567.76499999999999" ] = -567.765,
[ "12.3" ] = 12.3, [ "12.300000000000001" ] = 12.3,
[ "0" ] = 0, [ "0" ] = 0,
[ "0.10000000012" ] = 0.10000000012, [ "0.10000000012" ] = 0.10000000012,
} }
for k, v in pairs(t) do for k, v in pairs(t) do
local res = json.decode(k) local res = json.decode(k)