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

Pretty Encoding #32

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
119 changes: 88 additions & 31 deletions src/lunajson/encoder.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
local error = error
local byte, find, format, gsub, match = string.byte, string.find, string.format, string.gsub, string.match
local byte, find, format, gsub, match, rep = string.byte, string.find, string.format, string.gsub, string.match, string.rep
local concat = table.concat
local tostring = tostring
local pairs, type = pairs, type
local setmetatable = setmetatable
local huge, tiny = 1/0, -1/0

local set_cache = setmetatable({}, { __weak = "k" })
local basic = {
start_array = '[',
end_array = ']',
start_object = '{',
end_object = '}',
split_element = ','
}
local delim_tmpls = {
start_array = '[\n%s%%s',
end_array = '\n%%s]',
start_object = '{\n%s%%s',
end_object = '\n%%s}',
split_element = ',\n%s%%s'
}
local f_string_esc_pat
if _VERSION == "Lua 5.1" then
-- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly
Expand All @@ -15,12 +30,13 @@ else
end

local _ENV = nil

local one_space = " "

local function newencoder()
local v, nullv
local active_delims
local v, nullv, colon
local i, builder, visited

local function f_tostring(v)
builder[i] = tostring(v)
i = i+1
Expand Down Expand Up @@ -86,31 +102,33 @@ local function newencoder()
builder[i+2] = '"'
i = i+3
end

local function f_table(o)
local function basic_f_table(o)
if visited[o] then
error("loop detected")
end
visited[o] = true

local tmp = o[0]
if type(tmp) == 'number' then -- arraylen available
builder[i] = '['
i = i+1
for j = 1, tmp do
doencode(o[j])
builder[i] = ','
local arraylen = o[0]
if type(arraylen) == 'number' then -- arraylen available
if arraylen == 0 then
builder[i] = '[]'
else
builder[i] = active_delims.start_array
i = i+1
for j = 1, arraylen do
doencode(o[j])
builder[i] = active_delims.split_element
i = i+1
end
if arraylen > 0 then
i = i-1
end
builder[i] = active_delims.end_array
end
if tmp > 0 then
i = i-1
end
builder[i] = ']'

else
tmp = o[1]
if tmp ~= nil then -- detected as array
builder[i] = '['
builder[i] = active_delims.start_array
i = i+1
local j = 2
repeat
Expand All @@ -120,42 +138,44 @@ local function newencoder()
break
end
j = j+1
builder[i] = ','
builder[i] = active_delims.split_element
i = i+1
until false
builder[i] = ']'
builder[i] = active_delims.end_array

else -- detected as object
builder[i] = '{'
builder[i] = active_delims.start_object
i = i+1
local tmp = i
for k, v in pairs(o) do
if type(k) ~= 'string' then
error("non-string key")
end
f_string(k)
builder[i] = ':'
builder[i] = colon
i = i+1
doencode(v)
builder[i] = ','
builder[i] = active_delims.split_element
i = i+1
end
i = i-1
if i > tmp then
i = i-1
builder[i] = active_delims.end_object
else
builder[i] = '{}'
end
builder[i] = '}'
end
end

i = i+1
visited[o] = nil
end

local f_table
local dispatcher = {
boolean = f_tostring,
number = f_number,
string = f_string,
table = f_table,
table = function(...) return f_table(...) end,
__index = function()
error("invalid type value")
end
Expand All @@ -170,10 +190,47 @@ local function newencoder()
end
return dispatcher[type(v)](v)
end

local function encode(v_, nullv_)
v, nullv = v_, nullv_
local delim_set, space, depth
local delims_meta = {}
function delims_meta:__index(k)
local s = format(delim_set[k], rep(space, depth))
self[k] = s
return s
end
local function spaced_f_table(o)
depth = depth + 1
local delims = delim_set[depth]
if not delims then
delims = setmetatable({}, delims_meta)
delim_set[depth] = delims
end
active_delims = delims
basic_f_table(o)
depth = depth - 1
active_delims = delim_set[depth]
end
local function encode(v_, nullv_, _space)
v, nullv, space = v_, nullv_, _space
i, builder, visited = 1, {}, {}

if type(space) == "number" then space = rep(one_space, space) end
if type(space) ~= "string" or space == "" then
active_delims = basic
f_table = basic_f_table
colon = ":"
else
colon = ": "
depth = -1
f_table = spaced_f_table
delim_set = set_cache[space]
if not delim_set then
delim_set = {}
set_cache[space] = {}
for k, v in pairs(delim_tmpls) do
delim_set[k] = format(v, space)
end
end
end

doencode(v)
return concat(builder)
Expand Down