Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize quadratic to linear time complexity, various crucial fixes, fix benchmarks #46

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bench/bench_decode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ for i, name in ipairs(libs) do
end

-- 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
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)
end

Expand Down
4 changes: 2 additions & 2 deletions bench/bench_encode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ for i, name in ipairs(libs) do
end

-- 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
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)
end

Expand Down
4 changes: 4 additions & 0 deletions bench/util/bench.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ local fmt = string.format


function bench.run(name, count, func)
-- Ensure all garbage is collected
for _ = 1, 10 do -- cycles
collectgarbage()
end
-- Run bench
local res = {}
for i = 1, count do
Expand Down
76 changes: 46 additions & 30 deletions json.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,16 @@ local function escape_char(c)
end


local function encode_nil(val)
return "null"
local function encode_nil(rope)
rope[#rope + 1] = "null"
end


local function encode_table(val, stack)
local res = {}
stack = stack or {}

local function encode_table(rope, val, stack)
-- Circular reference?
if stack[val] then error("circular reference") end

stack[val] = true

if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
Expand All @@ -78,61 +74,80 @@ local function encode_table(val, stack)
error("invalid table: sparse array")
end
-- Encode
rope[#rope + 1] = "["
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
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"

rope[#rope + 1] = "]"
else
-- Treat as an object
rope[#rope + 1] = "{"
local first = true
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
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
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
rope[#rope + 1] = "}"
end
stack[val] = nil
end


local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
local function encode_string(rope, val)
rope[#rope + 1] = '"'
rope[#rope + 1] = val:gsub('[%z\1-\31\\"]', escape_char)
rope[#rope + 1] = '"'
end


local function encode_number(val)
local function encode_number(rope, val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
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

local function encode_boolean(rope, val)
rope[#rope + 1] = val and "true" or "false"
end

local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
[ "boolean" ] = encode_boolean,
}


encode = function(val, stack)
function encode(rope, val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
local encoder = type_func_map[t]
if encoder then
return encoder(rope, val, stack)
end
error("unexpected type '" .. t .. "'")
end


function json.encode(val)
return ( encode(val) )
local rope = {}
encode(rope, val, {})
return table.concat(rope)
end


Expand Down Expand Up @@ -216,7 +231,7 @@ end


local function parse_string(str, i)
local res = ""
local res = {}
local j = i + 1
local k = j

Expand All @@ -227,32 +242,33 @@ local function parse_string(str, i)
decode_error(str, j, "control character in string")

elseif x == 92 then -- `\`: Escape
res = res .. str:sub(k, j - 1)
res[#res + 1] = str:sub(k, j - 1)
j = j + 1
local c = str:sub(j, j)
if c == "u" then
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 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
else
if not escape_chars[c] then
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
end
res = res .. escape_char_map_inv[c]
res[#res + 1] = escape_char_map_inv[c]
end
k = j + 1

elseif x == 34 then -- `"`: End of string
res = res .. str:sub(k, j - 1)
return res, j + 1
res[#res + 1] = str:sub(k, j - 1)
return table.concat(res), j + 1
end

j = j + 1
end

decode_error(str, i, "expected closing quote for string")
return table.concat(res)
end


Expand Down
12 changes: 6 additions & 6 deletions test/test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ end

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