diff --git a/src/lunajson/encoder.lua b/src/lunajson/encoder.lua index 606767a..e221c41 100644 --- a/src/lunajson/encoder.lua +++ b/src/lunajson/encoder.lua @@ -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 @@ -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 @@ -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 @@ -120,13 +138,13 @@ 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 @@ -134,28 +152,30 @@ local function newencoder() 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 @@ -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)