mirror of
https://github.com/gvx/bitser.git
synced 2024-11-24 06:54:20 +00:00
530 lines
14 KiB
Lua
530 lines
14 KiB
Lua
--[[
|
|
Copyright (c) 2020, Jasmijn Wellner
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
]]
|
|
|
|
local VERSION = '1.1'
|
|
|
|
local floor = math.floor
|
|
local pairs = pairs
|
|
local type = type
|
|
local insert = table.insert
|
|
local getmetatable = getmetatable
|
|
local setmetatable = setmetatable
|
|
|
|
local ffi = require("ffi")
|
|
local buf_pos = 0
|
|
local buf_size = -1
|
|
local buf = nil
|
|
local buf_is_writable = true
|
|
local writable_buf = nil
|
|
local writable_buf_size = nil
|
|
local includeMetatables = true -- togglable with bitser.includeMetatables(false)
|
|
local SEEN_LEN = {}
|
|
|
|
local function Buffer_prereserve(min_size)
|
|
if buf_size < min_size then
|
|
buf_size = min_size
|
|
buf = ffi.new("uint8_t[?]", buf_size)
|
|
buf_is_writable = true
|
|
end
|
|
end
|
|
|
|
local function Buffer_clear()
|
|
buf_size = -1
|
|
buf = nil
|
|
buf_is_writable = true
|
|
writable_buf = nil
|
|
writable_buf_size = nil
|
|
end
|
|
|
|
local function Buffer_makeBuffer(size)
|
|
if not buf_is_writable then
|
|
buf = writable_buf
|
|
buf_size = writable_buf_size
|
|
writable_buf = nil
|
|
writable_buf_size = nil
|
|
buf_is_writable = true
|
|
end
|
|
buf_pos = 0
|
|
Buffer_prereserve(size)
|
|
end
|
|
|
|
local function Buffer_newReader(str)
|
|
Buffer_makeBuffer(#str)
|
|
ffi.copy(buf, str, #str)
|
|
end
|
|
|
|
local function Buffer_newDataReader(data, size)
|
|
if buf_is_writable then
|
|
writable_buf = buf
|
|
writable_buf_size = buf_size
|
|
end
|
|
buf_is_writable = false
|
|
buf_pos = 0
|
|
buf_size = size
|
|
buf = ffi.cast("uint8_t*", data)
|
|
end
|
|
|
|
local function Buffer_reserve(additional_size)
|
|
while buf_pos + additional_size > buf_size do
|
|
buf_size = buf_size * 2
|
|
local oldbuf = buf
|
|
buf = ffi.new("uint8_t[?]", buf_size)
|
|
buf_is_writable = true
|
|
ffi.copy(buf, oldbuf, buf_pos)
|
|
end
|
|
end
|
|
|
|
local function Buffer_write_byte(x)
|
|
Buffer_reserve(1)
|
|
buf[buf_pos] = x
|
|
buf_pos = buf_pos + 1
|
|
end
|
|
|
|
local function Buffer_write_raw(data, len)
|
|
Buffer_reserve(len)
|
|
ffi.copy(buf + buf_pos, data, len)
|
|
buf_pos = buf_pos + len
|
|
end
|
|
|
|
local function Buffer_write_string(s)
|
|
Buffer_write_raw(s, #s)
|
|
end
|
|
|
|
local function Buffer_write_data(ct, len, ...)
|
|
Buffer_write_raw(ffi.new(ct, ...), len)
|
|
end
|
|
|
|
local function Buffer_ensure(numbytes)
|
|
if buf_pos + numbytes > buf_size then
|
|
error("malformed serialized data")
|
|
end
|
|
end
|
|
|
|
local function Buffer_read_byte()
|
|
Buffer_ensure(1)
|
|
local x = buf[buf_pos]
|
|
buf_pos = buf_pos + 1
|
|
return x
|
|
end
|
|
|
|
local function Buffer_read_string(len)
|
|
Buffer_ensure(len)
|
|
local x = ffi.string(buf + buf_pos, len)
|
|
buf_pos = buf_pos + len
|
|
return x
|
|
end
|
|
|
|
local function Buffer_read_raw(data, len)
|
|
ffi.copy(data, buf + buf_pos, len)
|
|
buf_pos = buf_pos + len
|
|
return data
|
|
end
|
|
|
|
local function Buffer_read_data(ct, len)
|
|
return Buffer_read_raw(ffi.new(ct), len)
|
|
end
|
|
|
|
local resource_registry = {}
|
|
local resource_name_registry = {}
|
|
local class_registry = {}
|
|
local class_name_registry = {}
|
|
local classkey_registry = {}
|
|
local class_deserialize_registry = {}
|
|
local extension_registry = {}
|
|
local extensions_by_type = {}
|
|
local EXTENSION_TYPE_KEY = 'bitser-type'
|
|
local EXTENSION_MATCH_KEY = 'bitser-match'
|
|
local EXTENSION_LOAD_KEY = 'bitser-load'
|
|
local EXTENSION_DUMP_KEY = 'bitser-dump'
|
|
|
|
local serialize_value
|
|
|
|
local function write_number(value, _)
|
|
if floor(value) == value and value >= -2147483648 and value <= 2147483647 then
|
|
if value >= -27 and value <= 100 then
|
|
--small int
|
|
Buffer_write_byte(value + 27)
|
|
elseif value >= -32768 and value <= 32767 then
|
|
--short int
|
|
Buffer_write_byte(250)
|
|
Buffer_write_data("int16_t[1]", 2, value)
|
|
else
|
|
--long int
|
|
Buffer_write_byte(245)
|
|
Buffer_write_data("int32_t[1]", 4, value)
|
|
end
|
|
else
|
|
--double
|
|
Buffer_write_byte(246)
|
|
Buffer_write_data("double[1]", 8, value)
|
|
end
|
|
end
|
|
|
|
local function write_string(value, _)
|
|
if #value < 32 then
|
|
--short string
|
|
Buffer_write_byte(192 + #value)
|
|
else
|
|
--long string
|
|
Buffer_write_byte(244)
|
|
write_number(#value)
|
|
end
|
|
Buffer_write_string(value)
|
|
end
|
|
|
|
local function write_nil(_, _)
|
|
Buffer_write_byte(247)
|
|
end
|
|
|
|
local function write_boolean(value, _)
|
|
Buffer_write_byte(value and 249 or 248)
|
|
end
|
|
|
|
local function write_table(value, seen)
|
|
local classkey
|
|
local metatable = getmetatable(value)
|
|
local classname = (class_name_registry[value.class] -- MiddleClass
|
|
or class_name_registry[value.__baseclass] -- SECL
|
|
or class_name_registry[metatable] -- hump.class
|
|
or class_name_registry[value.__class__] -- Slither
|
|
or class_name_registry[value.__class]) -- Moonscript class
|
|
if classname then
|
|
classkey = classkey_registry[classname]
|
|
Buffer_write_byte(242)
|
|
serialize_value(classname, seen)
|
|
elseif includeMetatables and metatable then
|
|
Buffer_write_byte(253)
|
|
else
|
|
Buffer_write_byte(240)
|
|
end
|
|
local len = #value
|
|
write_number(len, seen)
|
|
for i = 1, len do
|
|
serialize_value(value[i], seen)
|
|
end
|
|
local klen = 0
|
|
for k in pairs(value) do
|
|
if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then
|
|
klen = klen + 1
|
|
end
|
|
end
|
|
write_number(klen, seen)
|
|
for k, v in pairs(value) do
|
|
if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then
|
|
serialize_value(k, seen)
|
|
serialize_value(v, seen)
|
|
end
|
|
end
|
|
if includeMetatables and metatable and not classname then
|
|
serialize_value(metatable, seen)
|
|
end
|
|
end
|
|
|
|
local function write_cdata(value, seen)
|
|
local ty = ffi.typeof(value)
|
|
if ty == value then
|
|
-- ctype
|
|
Buffer_write_byte(251)
|
|
serialize_value(tostring(ty):sub(7, -2), seen)
|
|
return
|
|
end
|
|
-- cdata
|
|
Buffer_write_byte(252)
|
|
serialize_value(ty, seen)
|
|
local len = ffi.sizeof(value)
|
|
write_number(len)
|
|
Buffer_write_raw(ffi.typeof('$[1]', ty)(value), len)
|
|
end
|
|
|
|
local types = {number = write_number, string = write_string, table = write_table, boolean = write_boolean, ["nil"] = write_nil, cdata = write_cdata}
|
|
|
|
serialize_value = function(value, seen)
|
|
if seen[value] then
|
|
local ref = seen[value]
|
|
if ref < 64 then
|
|
--small reference
|
|
Buffer_write_byte(128 + ref)
|
|
else
|
|
--long reference
|
|
Buffer_write_byte(243)
|
|
write_number(ref, seen)
|
|
end
|
|
return
|
|
end
|
|
local t = type(value)
|
|
if t ~= 'number' and t ~= 'boolean' and t ~= 'nil' and t ~= 'cdata' then
|
|
seen[value] = seen[SEEN_LEN]
|
|
seen[SEEN_LEN] = seen[SEEN_LEN] + 1
|
|
end
|
|
if resource_name_registry[value] then
|
|
local name = resource_name_registry[value]
|
|
if #name < 16 then
|
|
--small resource
|
|
Buffer_write_byte(224 + #name)
|
|
Buffer_write_string(name)
|
|
else
|
|
--long resource
|
|
Buffer_write_byte(241)
|
|
write_string(name, seen)
|
|
end
|
|
return
|
|
end
|
|
if extensions_by_type[t] then
|
|
for extension_id, extension in pairs(extensions_by_type[t]) do
|
|
if extension[EXTENSION_MATCH_KEY](value) then
|
|
-- extension
|
|
Buffer_write_byte(254)
|
|
serialize_value(extension_id, seen)
|
|
serialize_value(extension[EXTENSION_DUMP_KEY](value), seen)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
(types[t] or
|
|
error("cannot serialize type " .. t)
|
|
)(value, seen)
|
|
end
|
|
|
|
local function serialize(value)
|
|
Buffer_makeBuffer(4096)
|
|
local seen = {[SEEN_LEN] = 0}
|
|
serialize_value(value, seen)
|
|
end
|
|
|
|
local function add_to_seen(value, seen)
|
|
insert(seen, value)
|
|
return value
|
|
end
|
|
|
|
local function reserve_seen(seen)
|
|
insert(seen, 42)
|
|
return #seen
|
|
end
|
|
|
|
local function deserialize_value(seen)
|
|
local t = Buffer_read_byte()
|
|
if t < 128 then
|
|
--small int
|
|
return t - 27
|
|
elseif t < 192 then
|
|
--small reference
|
|
return seen[t - 127]
|
|
elseif t < 224 then
|
|
--small string
|
|
return add_to_seen(Buffer_read_string(t - 192), seen)
|
|
elseif t < 240 then
|
|
--small resource
|
|
return add_to_seen(resource_registry[Buffer_read_string(t - 224)], seen)
|
|
elseif t == 240 or t == 253 then
|
|
--table
|
|
local v = add_to_seen({}, seen)
|
|
local len = deserialize_value(seen)
|
|
for i = 1, len do
|
|
v[i] = deserialize_value(seen)
|
|
end
|
|
len = deserialize_value(seen)
|
|
for _ = 1, len do
|
|
local key = deserialize_value(seen)
|
|
v[key] = deserialize_value(seen)
|
|
end
|
|
if t == 253 then
|
|
if includeMetatables then
|
|
setmetatable(v, deserialize_value(seen))
|
|
end
|
|
end
|
|
return v
|
|
elseif t == 241 then
|
|
--long resource
|
|
local idx = reserve_seen(seen)
|
|
local value = resource_registry[deserialize_value(seen)]
|
|
seen[idx] = value
|
|
return value
|
|
elseif t == 242 then
|
|
--instance
|
|
local instance = add_to_seen({}, seen)
|
|
local classname = deserialize_value(seen)
|
|
local class = class_registry[classname]
|
|
local classkey = classkey_registry[classname]
|
|
local deserializer = class_deserialize_registry[classname]
|
|
local len = deserialize_value(seen)
|
|
for i = 1, len do
|
|
instance[i] = deserialize_value(seen)
|
|
end
|
|
len = deserialize_value(seen)
|
|
for _ = 1, len do
|
|
local key = deserialize_value(seen)
|
|
instance[key] = deserialize_value(seen)
|
|
end
|
|
if classkey then
|
|
instance[classkey] = class
|
|
end
|
|
return deserializer(instance, class)
|
|
elseif t == 243 then
|
|
--reference
|
|
return seen[deserialize_value(seen) + 1]
|
|
elseif t == 244 then
|
|
--long string
|
|
return add_to_seen(Buffer_read_string(deserialize_value(seen)), seen)
|
|
elseif t == 245 then
|
|
--long int
|
|
return Buffer_read_data("int32_t[1]", 4)[0]
|
|
elseif t == 246 then
|
|
--double
|
|
return Buffer_read_data("double[1]", 8)[0]
|
|
elseif t == 247 then
|
|
--nil
|
|
return nil
|
|
elseif t == 248 then
|
|
--false
|
|
return false
|
|
elseif t == 249 then
|
|
--true
|
|
return true
|
|
elseif t == 250 then
|
|
--short int
|
|
return Buffer_read_data("int16_t[1]", 2)[0]
|
|
elseif t == 251 then
|
|
--ctype
|
|
return ffi.typeof(deserialize_value(seen))
|
|
elseif t == 252 then
|
|
local ctype = deserialize_value(seen)
|
|
local len = deserialize_value(seen)
|
|
local read_into = ffi.typeof('$[1]', ctype)()
|
|
Buffer_read_raw(read_into, len)
|
|
return ctype(read_into[0])
|
|
elseif t == 254 then
|
|
--extension
|
|
local extension_id = deserialize_value(seen)
|
|
return extension_registry[extension_id][EXTENSION_LOAD_KEY](deserialize_value(seen))
|
|
else
|
|
error("unsupported serialized type " .. t)
|
|
end
|
|
end
|
|
|
|
local function deserialize_MiddleClass(instance, class)
|
|
return setmetatable(instance, class.__instanceDict)
|
|
end
|
|
|
|
local function deserialize_SECL(instance, class)
|
|
return setmetatable(instance, getmetatable(class))
|
|
end
|
|
|
|
local deserialize_humpclass = setmetatable
|
|
|
|
local function deserialize_Slither(instance, class)
|
|
return getmetatable(class).allocate(instance)
|
|
end
|
|
|
|
local function deserialize_Moonscript(instance, class)
|
|
return setmetatable(instance, class.__base)
|
|
end
|
|
|
|
return {dumps = function(value)
|
|
serialize(value)
|
|
return ffi.string(buf, buf_pos)
|
|
end, dumpLoveFile = function(fname, value)
|
|
serialize(value)
|
|
assert(love.filesystem.write(fname, ffi.string(buf, buf_pos)))
|
|
end, loadLoveFile = function(fname)
|
|
local serializedData, error = love.filesystem.newFileData(fname)
|
|
assert(serializedData, error)
|
|
Buffer_newDataReader(serializedData:getPointer(), serializedData:getSize())
|
|
local value = deserialize_value({})
|
|
-- serializedData needs to not be collected early in a tail-call
|
|
-- so make sure deserialize_value returns before loadLoveFile does
|
|
return value
|
|
end, loadData = function(data, size)
|
|
if size == 0 then
|
|
error('cannot load value from empty data')
|
|
end
|
|
Buffer_newDataReader(data, size)
|
|
return deserialize_value({})
|
|
end, loads = function(str)
|
|
if #str == 0 then
|
|
error('cannot load value from empty string')
|
|
end
|
|
Buffer_newReader(str)
|
|
return deserialize_value({})
|
|
end, includeMetatables = function(bool)
|
|
includeMetatables = not not bool
|
|
end, register = function(name, resource)
|
|
assert(not resource_registry[name], name .. " already registered")
|
|
resource_registry[name] = resource
|
|
resource_name_registry[resource] = name
|
|
return resource
|
|
end, unregister = function(name)
|
|
resource_name_registry[resource_registry[name]] = nil
|
|
resource_registry[name] = nil
|
|
end, registerClass = function(name, class, classkey, deserializer)
|
|
if not class then
|
|
class = name
|
|
name = class.__name__ or class.name or class.__name
|
|
end
|
|
if not classkey then
|
|
if class.__instanceDict then
|
|
-- assume MiddleClass
|
|
classkey = 'class'
|
|
elseif class.__baseclass then
|
|
-- assume SECL
|
|
classkey = '__baseclass'
|
|
end
|
|
-- assume hump.class, Slither, Moonscript class or something else that doesn't store the
|
|
-- class directly on the instance
|
|
end
|
|
if not deserializer then
|
|
if class.__instanceDict then
|
|
-- assume MiddleClass
|
|
deserializer = deserialize_MiddleClass
|
|
elseif class.__baseclass then
|
|
-- assume SECL
|
|
deserializer = deserialize_SECL
|
|
elseif class.__index == class then
|
|
-- assume hump.class
|
|
deserializer = deserialize_humpclass
|
|
elseif class.__name__ then
|
|
-- assume Slither
|
|
deserializer = deserialize_Slither
|
|
elseif class.__base then
|
|
-- assume Moonscript class
|
|
deserializer = deserialize_Moonscript
|
|
else
|
|
error("no deserializer given for unsupported class library")
|
|
end
|
|
end
|
|
class_registry[name] = class
|
|
classkey_registry[name] = classkey
|
|
class_deserialize_registry[name] = deserializer
|
|
class_name_registry[class] = name
|
|
return class
|
|
end, unregisterClass = function(name)
|
|
class_name_registry[class_registry[name]] = nil
|
|
classkey_registry[name] = nil
|
|
class_deserialize_registry[name] = nil
|
|
class_registry[name] = nil
|
|
end, registerExtension = function(extension_id, extension)
|
|
assert(not extension_registry[extension_id], 'extension with id ' .. extension_id .. ' already registered')
|
|
local ty = extension[EXTENSION_TYPE_KEY]
|
|
assert(type(ty) == 'string' and type(extension[EXTENSION_MATCH_KEY]) == 'function' and type(extension[EXTENSION_LOAD_KEY]) == 'function' and type(extension[EXTENSION_DUMP_KEY]) == 'function', 'not a valid extension')
|
|
extension_registry[extension_id] = extension
|
|
if not extensions_by_type[ty] then
|
|
extensions_by_type[ty] = {}
|
|
end
|
|
extensions_by_type[ty][extension_id] = extension
|
|
end, unregisterExtension = function(extension_id)
|
|
extensions_by_type[extension_registry[extension_id][EXTENSION_TYPE_KEY]][extension_id] = nil
|
|
extension_registry[extension_id] = nil
|
|
end, reserveBuffer = Buffer_prereserve, clearBuffer = Buffer_clear, version = VERSION}
|