-- by "Yevano", tweaked due to issues with signed integer handling --[[ BLODS - Binary Lua Object (De)Serialization ]] --[[ Save on table access. ]] local pairs = pairs local type = type local loadstring = loadstring local mathabs = math.abs local mathfloor = math.floor local mathfrexp = math.frexp local mathmodf = math.modf local mathpow = math.pow local stringbyte = string.byte local stringchar = string.char local stringdump = string.dump local stringsub = string.sub local tableconcat = table.concat --[[ Float conversions. Modified from http://snippets.luacode.org/snippets/IEEE_float_conversion_144. ]] local function double2str(value) local s=value<0 and 1 or 0 if mathabs(value)==1/0 then return (s==1 and "\0\0\0\0\0\0\240\255" or "\0\0\0\0\0\0\240\127") end if value~=value then return "\170\170\170\170\170\170\250\255" end local fr,exp=mathfrexp(mathabs(value)) fr,exp=fr*2,exp-1 exp=exp+1023 return tableconcat({stringchar(mathfloor(fr*2^52)%256), stringchar(mathfloor(fr*2^44)%256), stringchar(mathfloor(fr*2^36)%256), stringchar(mathfloor(fr*2^28)%256), stringchar(mathfloor(fr*2^20)%256), stringchar(mathfloor(fr*2^12)%256), stringchar(mathfloor(fr*2^4)%16+mathfloor(exp)%16*16), stringchar(mathfloor(exp/2^4)%128+128*s)}) end local function str2double(str) local fr=stringbyte(str, 1)/2^52+stringbyte(str, 2)/2^44+stringbyte(str, 3)/2^36+stringbyte(str, 4)/2^28+stringbyte(str, 5)/2^20+stringbyte(str, 6)/2^12+(stringbyte(str, 7)%16)/2^4+1 local exp=(stringbyte(str, 8)%128)*16+mathfloor(str:byte(7)/16)-1023 local s=mathfloor(stringbyte(str, 8)/128) if exp==1024 then return fr==1 and (1-2*s)/0 or 0/0 end return (1-2*s)*fr*2^exp end --[[ Integer conversions. Taken from http://lua-users.org/wiki/ReadWriteFormat. Modified to support signed ints. ]] local function signedstringtonumber(str) local function _b2n(exp, num, digit, ...) if not digit then return num end return _b2n(exp*256, num + digit*exp, ...) end return _b2n(256, stringbyte(str, 1, -1)) - mathpow(2, #str * 8 - 1) end local function stringtonumber(str) local function _b2n(exp, num, digit, ...) if not digit then return num end return _b2n(exp*256, num + digit*exp, ...) end return _b2n(256, stringbyte(str, 1, -1)) end local function numbertobytes(num, width) local function _n2b(width, num, rem) rem = rem * 256 if width == 0 then return rem end return rem, _n2b(width-1, mathmodf(num/256)) end return stringchar(_n2b(width-1, mathmodf((num)/256))) end local function log2(x) return math.log10(x) / math.log10(2) end --[[ (De)Serialization for Lua types. ]] local function intWidth(int) local out = math.ceil((log2(int) + 1) / 8) if out == math.huge or out == -math.huge then return 1 end return out end local types = { boolean = "b", double = "d", posinteger = "p", neginteger = "n", string = "s", table = "t", ["function"] = "f", ["nil"] = "_" } local serialization = { } local deserialization = { } function serialization.boolean(obj) return obj and "\1" or "\0" end function serialization.double(obj) return double2str(obj) end function serialization.integer(obj) local width = intWidth(obj) return stringchar(width) .. numbertobytes(obj, width) end function serialization.string(obj) local len = #obj local width = intWidth(len) return tableconcat({ stringchar(width), numbertobytes(len, width), obj }) end serialization["function"] = function(obj) local ok, s = pcall(stringdump, obj) if not ok then return "_" end return numbertobytes(#s, 4) .. s end function deserialization.b(idx, ser) local ret = stringsub(ser[1], idx, idx) == "\1" return ret, idx + 1 end function deserialization.d(idx, ser) local ret = str2double(stringsub(ser[1], idx, idx + 8)) return ret, idx + 8 end function deserialization.p(idx, ser) local width = stringtonumber(stringsub(ser[1], idx, idx)) local ret = stringtonumber(stringsub(ser[1], idx + 1, idx + width)) return ret, idx + width + 1 end function deserialization.n(idx, ser) local width = stringtonumber(stringsub(ser[1], idx, idx)) local ret = stringtonumber(stringsub(ser[1], idx + 1, idx + width)) return -ret, idx + width + 1 end function deserialization.s(idx, ser) local width = stringtonumber(stringsub(ser[1], idx, idx)) local len = stringtonumber(stringsub(ser[1], idx + 1, idx + width)) local ret = stringsub(ser[1], idx + width + 1, idx + width + len) return ret, idx + width + len + 1 end function deserialization.f(idx, ser) local len = stringtonumber(stringsub(ser[1], idx, idx + 3)) local ret = loadstring(stringsub(ser[1], idx + 4, idx + len + 3)) return ret, idx + len + 4 end function deserialization._(idx, ser) return nil, idx end local function yield() os.queueEvent "" os.pullEvent "" end if not os.queueEvent then yield = function() end end function serialize(obj) -- State vars. local ntables = 1 local tables = { } local tableIDs = { } local tableSerial = { } -- Internal recursive function. local function serialize(obj) yield() local t = type(obj) if t == "table" then local len = #obj if tables[obj] then -- We already serialized this table. Just return the id. return tableIDs[obj] end -- Insert table info. local id = ntables tables[obj] = true local width = intWidth(ntables) local ser = "t" .. numbertobytes(width, 1) .. numbertobytes(ntables, width) tableIDs[obj] = ser -- Important to increment here so tables inside this one don't use the same id. ntables = ntables + 1 -- Serialize the table. local serialConcat = { } -- Array part. for i = 1, len do if obj[i] == nil then len = i - 1 break end serialConcat[#serialConcat + 1] = serialize(obj[i]) end serialConcat[#serialConcat + 1] = "\0" -- Table part. for k, v in pairs(obj) do if type(k) ~= "number" or ((k > len or k < 1) or mathfloor(k) ~= k) then -- For each pair, serialize both the key and the value. local idx = #serialConcat serialConcat[idx + 1] = serialize(k) serialConcat[idx + 2] = serialize(v) end end serialConcat[#serialConcat + 1] = "\0" -- tableconcat is way faster than normal concatenation using .. when dealing with lots of strings. -- Add this serialization to the table of serialized tables for quick access and later more concatenation. tableSerial[id] = tableconcat(serialConcat) return ser else -- Do serialization on a non-recursive type. if t == "number" then -- Space optimization can be done for ints, so serialize them differently from doubles. -- OSMARKS EDIT: handle sign in type and not actual serialization if mathfloor(obj) == obj then local intval = serialization.integer(math.abs(obj)) local typespec = "p" if obj < 0 then typespec = "n" end return typespec .. intval end return "d" .. serialization.double(obj) end local ser = types[t] return obj == nil and ser or ser .. serialization[t](obj) end end -- Either serialize for a table or for a non-recursive type. local ser = serialize(obj) if type(obj) == "table" then return tableconcat({ "t", tableconcat(tableSerial) }) end return ser end function deserialize(ser) local idx = 1 local tables = { { } } local serref = { ser } local function getchar() local ret = stringsub(serref[1], idx, idx) return ret ~= "" and ret or nil end local function deserializeValue() yield() local t = getchar() idx = idx + 1 if t == "t" then -- Get table id. local width = stringtonumber(getchar()) idx = idx + 1 local id = stringtonumber(stringsub(serref[1], idx, idx + width - 1)) idx = idx + width -- Create an empty table as a placeholder. if not tables[id] then tables[id] = { } end return tables[id] else local ret ret, idx = deserialization[t](idx, serref) return ret end end -- Either deserialize for a table or for a non-recursive type. local i = 1 if getchar() == "t" then idx = idx + 1 while getchar() do if not tables[i] then tables[i] = { } end local curtbl = tables[i] -- Array part. while getchar() ~= "\0" do curtbl[#curtbl + 1] = deserializeValue() end -- Table part. idx = idx + 1 while getchar() ~= "\0" do curtbl[deserializeValue()] = deserializeValue() end i = i + 1 idx = idx + 1 end return tables[1] end return deserializeValue() end return { serialize = serialize, deserialize = deserialize }