Mercurial > wow > askmrrobot
diff Libs/AceSerializer-3.0/AceSerializer-3.0.lua @ 57:01b63b8ed811 v21
total rewrite to version 21
author | yellowfive |
---|---|
date | Fri, 05 Jun 2015 11:05:15 -0700 |
parents | |
children | e635cd648e01 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/AceSerializer-3.0/AceSerializer-3.0.lua Fri Jun 05 11:05:15 2015 -0700 @@ -0,0 +1,283 @@ +--- **AceSerializer-3.0** can serialize any variable (except functions or userdata) into a string format, +-- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially +-- very large numbers or floating point numbers, and table structures. The only caveat currently is, that multiple +-- references to the same table will be send individually. +-- +-- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer:Embed(MyAddon) or by +-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object +-- and can be accessed directly, without having to explicitly call AceSerializer itself.\\ +-- It is recommended to embed AceSerializer, otherwise you'll have to specify a custom `self` on all calls you +-- make into AceSerializer. +-- @class file +-- @name AceSerializer-3.0 +-- @release $Id: AceSerializer-3.0.lua 1038 2011-10-03 01:39:58Z mikk $ +local MAJOR,MINOR = "AceSerializer-3.0", 4 +local AceSerializer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceSerializer then return end + +-- Lua APIs +local strbyte, strchar, gsub, gmatch, format = string.byte, string.char, string.gsub, string.gmatch, string.format +local assert, error, pcall = assert, error, pcall +local type, tostring, tonumber = type, tostring, tonumber +local pairs, select, frexp = pairs, select, math.frexp +local tconcat = table.concat + +-- quick copies of string representations of wonky numbers +local inf = math.huge + +local serNaN -- can't do this in 4.3, see ace3 ticket 268 +local serInf = tostring(inf) +local serNegInf = tostring(-inf) + + +-- Serialization functions + +local function SerializeStringHelper(ch) -- Used by SerializeValue for strings + -- We use \126 ("~") as an escape character for all nonprints plus a few more + local n = strbyte(ch) + if n==30 then -- v3 / ticket 115: catch a nonprint that ends up being "~^" when encoded... DOH + return "\126\122" + elseif n<=32 then -- nonprint + space + return "\126"..strchar(n+64) + elseif n==94 then -- value separator + return "\126\125" + elseif n==126 then -- our own escape character + return "\126\124" + elseif n==127 then -- nonprint (DEL) + return "\126\123" + else + assert(false) -- can't be reached if caller uses a sane regex + end +end + +local function SerializeValue(v, res, nres) + -- We use "^" as a value separator, followed by one byte for type indicator + local t=type(v) + + if t=="string" then -- ^S = string (escaped to remove nonprints, "^"s, etc) + res[nres+1] = "^S" + res[nres+2] = gsub(v,"[%c \94\126\127]", SerializeStringHelper) + nres=nres+2 + + elseif t=="number" then -- ^N = number (just tostring()ed) or ^F (float components) + local str = tostring(v) + if tonumber(str)==v --[[not in 4.3 or str==serNaN]] or str==serInf or str==serNegInf then + -- translates just fine, transmit as-is + res[nres+1] = "^N" + res[nres+2] = str + nres=nres+2 + else + local m,e = frexp(v) + res[nres+1] = "^F" + res[nres+2] = format("%.0f",m*2^53) -- force mantissa to become integer (it's originally 0.5--0.9999) + res[nres+3] = "^f" + res[nres+4] = tostring(e-53) -- adjust exponent to counteract mantissa manipulation + nres=nres+4 + end + + elseif t=="table" then -- ^T...^t = table (list of key,value pairs) + nres=nres+1 + res[nres] = "^T" + for k,v in pairs(v) do + nres = SerializeValue(k, res, nres) + nres = SerializeValue(v, res, nres) + end + nres=nres+1 + res[nres] = "^t" + + elseif t=="boolean" then -- ^B = true, ^b = false + nres=nres+1 + if v then + res[nres] = "^B" -- true + else + res[nres] = "^b" -- false + end + + elseif t=="nil" then -- ^Z = nil (zero, "N" was taken :P) + nres=nres+1 + res[nres] = "^Z" + + else + error(MAJOR..": Cannot serialize a value of type '"..t.."'") -- can't produce error on right level, this is wildly recursive + end + + return nres +end + + + +local serializeTbl = { "^1" } -- "^1" = Hi, I'm data serialized by AceSerializer protocol rev 1 + +--- Serialize the data passed into the function. +-- Takes a list of values (strings, numbers, booleans, nils, tables) +-- and returns it in serialized form (a string).\\ +-- May throw errors on invalid data types. +-- @param ... List of values to serialize +-- @return The data in its serialized form (string) +function AceSerializer:Serialize(...) + local nres = 1 + + for i=1,select("#", ...) do + local v = select(i, ...) + nres = SerializeValue(v, serializeTbl, nres) + end + + serializeTbl[nres+1] = "^^" -- "^^" = End of serialized data + + return tconcat(serializeTbl, "", 1, nres+1) +end + +-- Deserialization functions +local function DeserializeStringHelper(escape) + if escape<"~\122" then + return strchar(strbyte(escape,2,2)-64) + elseif escape=="~\122" then -- v3 / ticket 115: special case encode since 30+64=94 ("^") - OOPS. + return "\030" + elseif escape=="~\123" then + return "\127" + elseif escape=="~\124" then + return "\126" + elseif escape=="~\125" then + return "\94" + end + error("DeserializeStringHelper got called for '"..escape.."'?!?") -- can't be reached unless regex is screwed up +end + +local function DeserializeNumberHelper(number) + --[[ not in 4.3 if number == serNaN then + return 0/0 + else]]if number == serNegInf then + return -inf + elseif number == serInf then + return inf + else + return tonumber(number) + end +end + +-- DeserializeValue: worker function for :Deserialize() +-- It works in two modes: +-- Main (top-level) mode: Deserialize a list of values and return them all +-- Recursive (table) mode: Deserialize only a single value (_may_ of course be another table with lots of subvalues in it) +-- +-- The function _always_ works recursively due to having to build a list of values to return +-- +-- Callers are expected to pcall(DeserializeValue) to trap errors + +local function DeserializeValue(iter,single,ctl,data) + + if not single then + ctl,data = iter() + end + + if not ctl then + error("Supplied data misses AceSerializer terminator ('^^')") + end + + if ctl=="^^" then + -- ignore extraneous data + return + end + + local res + + if ctl=="^S" then + res = gsub(data, "~.", DeserializeStringHelper) + elseif ctl=="^N" then + res = DeserializeNumberHelper(data) + if not res then + error("Invalid serialized number: '"..tostring(data).."'") + end + elseif ctl=="^F" then -- ^F<mantissa>^f<exponent> + local ctl2,e = iter() + if ctl2~="^f" then + error("Invalid serialized floating-point number, expected '^f', not '"..tostring(ctl2).."'") + end + local m=tonumber(data) + e=tonumber(e) + if not (m and e) then + error("Invalid serialized floating-point number, expected mantissa and exponent, got '"..tostring(m).."' and '"..tostring(e).."'") + end + res = m*(2^e) + elseif ctl=="^B" then -- yeah yeah ignore data portion + res = true + elseif ctl=="^b" then -- yeah yeah ignore data portion + res = false + elseif ctl=="^Z" then -- yeah yeah ignore data portion + res = nil + elseif ctl=="^T" then + -- ignore ^T's data, future extensibility? + res = {} + local k,v + while true do + ctl,data = iter() + if ctl=="^t" then break end -- ignore ^t's data + k = DeserializeValue(iter,true,ctl,data) + if k==nil then + error("Invalid AceSerializer table format (no table end marker)") + end + ctl,data = iter() + v = DeserializeValue(iter,true,ctl,data) + if v==nil then + error("Invalid AceSerializer table format (no table end marker)") + end + res[k]=v + end + else + error("Invalid AceSerializer control code '"..ctl.."'") + end + + if not single then + return res,DeserializeValue(iter) + else + return res + end +end + +--- Deserializes the data into its original values. +-- Accepts serialized data, ignoring all control characters and whitespace. +-- @param str The serialized data (from :Serialize) +-- @return true followed by a list of values, OR false followed by an error message +function AceSerializer:Deserialize(str) + str = gsub(str, "[%c ]", "") -- ignore all control characters; nice for embedding in email and stuff + + local iter = gmatch(str, "(^.)([^^]*)") -- Any ^x followed by string of non-^ + local ctl,data = iter() + if not ctl or ctl~="^1" then + -- we purposefully ignore the data portion of the start code, it can be used as an extension mechanism + return false, "Supplied data is not AceSerializer data (rev 1)" + end + + return pcall(DeserializeValue, iter) +end + + +---------------------------------------- +-- Base library stuff +---------------------------------------- + +AceSerializer.internals = { -- for test scripts + SerializeValue = SerializeValue, + SerializeStringHelper = SerializeStringHelper, +} + +local mixins = { + "Serialize", + "Deserialize", +} + +AceSerializer.embeds = AceSerializer.embeds or {} + +function AceSerializer:Embed(target) + for k, v in pairs(mixins) do + target[v] = self[v] + end + self.embeds[target] = true + return target +end + +-- Update embeds +for target, v in pairs(AceSerializer.embeds) do + AceSerializer:Embed(target) +end \ No newline at end of file