mirror of
https://github.com/appgurueu/modlib.git
synced 2024-11-22 07:13:45 +01:00
189 lines
4.4 KiB
Lua
189 lines
4.4 KiB
Lua
local assert, next, pairs, pcall, error, type, table_insert, table_concat, string_format, string_match, setmetatable, select, setfenv, math_huge, loadfile, loadstring
|
|
= assert, next, pairs, pcall, error, type, table.insert, table.concat, string.format, string.match, setmetatable, select, setfenv, math.huge, loadfile, loadstring
|
|
|
|
local count_objects = modlib.table.count_objects
|
|
local is_identifier = modlib.text.is_identifier
|
|
|
|
-- Build a table with the succeeding character from A-Z
|
|
local succ = {}
|
|
for char = ("A"):byte(), ("Z"):byte() - 1 do
|
|
succ[string.char(char)] = string.char(char + 1)
|
|
end
|
|
|
|
local function quote(string)
|
|
return string_format("%q", string)
|
|
end
|
|
|
|
local _ENV = {}
|
|
setfenv(1, _ENV)
|
|
local metatable = {__index = _ENV}
|
|
_ENV.metatable = metatable
|
|
|
|
function new(self)
|
|
return setmetatable(self, metatable)
|
|
end
|
|
|
|
function aux_write(_self, _object)
|
|
-- returns reader, arguments
|
|
return
|
|
end
|
|
|
|
aux_read = {}
|
|
|
|
function write(self, value, write)
|
|
local reference = {"A"}
|
|
local function increment_reference(place)
|
|
if not reference[place] then
|
|
reference[place] = "B"
|
|
elseif reference[place] == "Z" then
|
|
reference[place] = "A"
|
|
return increment_reference(place + 1)
|
|
else
|
|
reference[place] = succ[reference[place]]
|
|
end
|
|
end
|
|
local references = {}
|
|
local to_fill = {}
|
|
-- TODO sort objects by count, give frequently referenced objects shorter references
|
|
for object, count in pairs(count_objects(value)) do
|
|
local type_ = type(object)
|
|
if count >= 2 and (type_ ~= "string" or #reference + 2 < #object) then
|
|
local ref = table_concat(reference)
|
|
write(ref)
|
|
write"="
|
|
write(type_ == "table" and "{}" or quote(object))
|
|
write";"
|
|
references[object] = ref
|
|
if type_ == "table" then
|
|
to_fill[object] = ref
|
|
end
|
|
increment_reference(1)
|
|
end
|
|
end
|
|
local function is_short_key(key)
|
|
return not references[key] and type(key) == "string" and is_identifier(key)
|
|
end
|
|
local function dump(value)
|
|
-- Primitive types
|
|
if value == nil then
|
|
return write"nil"
|
|
end
|
|
if value == true then
|
|
return write"true"
|
|
end
|
|
if value == false then
|
|
return write"false"
|
|
end
|
|
local type_ = type(value)
|
|
if type_ == "number" then
|
|
return write(string_format("%.17g", value))
|
|
end
|
|
-- Reference types: table and string
|
|
local ref = references[value]
|
|
if ref then
|
|
-- Referenced
|
|
return write(ref)
|
|
elseif type_ == "string" then
|
|
return write(quote(value))
|
|
elseif type_ == "table" then
|
|
local first = true
|
|
write"{"
|
|
local len = #value
|
|
for i = 1, len do
|
|
if not first then write";" end
|
|
dump(value[i])
|
|
first = false
|
|
end
|
|
for k, v in next, value do
|
|
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then
|
|
if not first then write";" end
|
|
if is_short_key(k) then
|
|
write(k)
|
|
else
|
|
write"["
|
|
dump(k)
|
|
write"]"
|
|
end
|
|
write"="
|
|
dump(v)
|
|
first = false
|
|
end
|
|
end
|
|
write"}"
|
|
else
|
|
-- TODO move aux_write to start, to allow dealing with metatables etc.?
|
|
(function(func, ...)
|
|
-- functions are the only way to deal with varargs
|
|
if not func then
|
|
return error("unsupported type: " .. type_)
|
|
end
|
|
write(func)
|
|
write"("
|
|
local n = select("#", ...)
|
|
local args = {...}
|
|
for i = 1, n - 1 do
|
|
dump(args[i])
|
|
write","
|
|
end
|
|
if n > 0 then
|
|
write(args[n])
|
|
end
|
|
write")"
|
|
end)(self:aux_write(value))
|
|
end
|
|
end
|
|
for table, ref in pairs(to_fill) do
|
|
for k, v in pairs(table) do
|
|
write(ref)
|
|
if is_short_key(k) then
|
|
write"."
|
|
write(k)
|
|
else
|
|
write"["
|
|
dump(k)
|
|
write"]"
|
|
end
|
|
write"="
|
|
dump(v)
|
|
write";"
|
|
end
|
|
end
|
|
write"return "
|
|
dump(value)
|
|
end
|
|
|
|
function write_file(self, value, file)
|
|
return self:write(value, function(text)
|
|
file:write(text)
|
|
end)
|
|
end
|
|
|
|
function write_string(self, value)
|
|
local rope = {}
|
|
self:write(value, function(text)
|
|
table_insert(rope, text)
|
|
end)
|
|
return table_concat(rope)
|
|
end
|
|
|
|
function read(self, ...)
|
|
local read = assert(...)
|
|
-- math.huge is serialized to inf, 0/0 is serialized to -nan
|
|
-- TODO verify this is actually the case, see https://en.wikipedia.org/wiki/Printf_format_string
|
|
setfenv(read, setmetatable({inf = math_huge, nan = 0/0}, {__index = self.aux_read}))
|
|
local success, value_or_err = pcall(read)
|
|
if success then
|
|
return value_or_err
|
|
end
|
|
return nil, value_or_err
|
|
end
|
|
|
|
function read_file(self, path)
|
|
return self:read(loadfile(path))
|
|
end
|
|
|
|
function read_string(self, string)
|
|
return self:read(loadstring(string))
|
|
end
|
|
|
|
return _ENV |