| yellowfive@57 | 1 --- **AceSerializer-3.0** can serialize any variable (except functions or userdata) into a string format, | 
| yellowfive@57 | 2 -- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially | 
| yellowfive@57 | 3 -- very large numbers or floating point numbers, and table structures. The only caveat currently is, that multiple | 
| yellowfive@57 | 4 -- references to the same table will be send individually. | 
| yellowfive@57 | 5 -- | 
| yellowfive@57 | 6 -- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer:Embed(MyAddon) or by | 
| yellowfive@57 | 7 -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object | 
| yellowfive@57 | 8 -- and can be accessed directly, without having to explicitly call AceSerializer itself.\\ | 
| yellowfive@57 | 9 -- It is recommended to embed AceSerializer, otherwise you'll have to specify a custom `self` on all calls you | 
| yellowfive@57 | 10 -- make into AceSerializer. | 
| yellowfive@57 | 11 -- @class file | 
| yellowfive@57 | 12 -- @name AceSerializer-3.0 | 
| yellowfive@57 | 13 -- @release $Id: AceSerializer-3.0.lua 1038 2011-10-03 01:39:58Z mikk $ | 
| yellowfive@57 | 14 local MAJOR,MINOR = "AceSerializer-3.0", 4 | 
| yellowfive@57 | 15 local AceSerializer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) | 
| yellowfive@57 | 16 | 
| yellowfive@57 | 17 if not AceSerializer then return end | 
| yellowfive@57 | 18 | 
| yellowfive@57 | 19 -- Lua APIs | 
| yellowfive@57 | 20 local strbyte, strchar, gsub, gmatch, format = string.byte, string.char, string.gsub, string.gmatch, string.format | 
| yellowfive@57 | 21 local assert, error, pcall = assert, error, pcall | 
| yellowfive@57 | 22 local type, tostring, tonumber = type, tostring, tonumber | 
| yellowfive@57 | 23 local pairs, select, frexp = pairs, select, math.frexp | 
| yellowfive@57 | 24 local tconcat = table.concat | 
| yellowfive@57 | 25 | 
| yellowfive@57 | 26 -- quick copies of string representations of wonky numbers | 
| yellowfive@57 | 27 local inf = math.huge | 
| yellowfive@57 | 28 | 
| yellowfive@57 | 29 local serNaN  -- can't do this in 4.3, see ace3 ticket 268 | 
| yellowfive@57 | 30 local serInf = tostring(inf) | 
| yellowfive@57 | 31 local serNegInf = tostring(-inf) | 
| yellowfive@57 | 32 | 
| yellowfive@57 | 33 | 
| yellowfive@57 | 34 -- Serialization functions | 
| yellowfive@57 | 35 | 
| yellowfive@57 | 36 local function SerializeStringHelper(ch)	-- Used by SerializeValue for strings | 
| yellowfive@57 | 37 	-- We use \126 ("~") as an escape character for all nonprints plus a few more | 
| yellowfive@57 | 38 	local n = strbyte(ch) | 
| yellowfive@57 | 39 	if n==30 then           -- v3 / ticket 115: catch a nonprint that ends up being "~^" when encoded... DOH | 
| yellowfive@57 | 40 		return "\126\122" | 
| yellowfive@57 | 41 	elseif n<=32 then 			-- nonprint + space | 
| yellowfive@57 | 42 		return "\126"..strchar(n+64) | 
| yellowfive@57 | 43 	elseif n==94 then		-- value separator | 
| yellowfive@57 | 44 		return "\126\125" | 
| yellowfive@57 | 45 	elseif n==126 then		-- our own escape character | 
| yellowfive@57 | 46 		return "\126\124" | 
| yellowfive@57 | 47 	elseif n==127 then		-- nonprint (DEL) | 
| yellowfive@57 | 48 		return "\126\123" | 
| yellowfive@57 | 49 	else | 
| yellowfive@57 | 50 		assert(false)	-- can't be reached if caller uses a sane regex | 
| yellowfive@57 | 51 	end | 
| yellowfive@57 | 52 end | 
| yellowfive@57 | 53 | 
| yellowfive@57 | 54 local function SerializeValue(v, res, nres) | 
| yellowfive@57 | 55 	-- We use "^" as a value separator, followed by one byte for type indicator | 
| yellowfive@57 | 56 	local t=type(v) | 
| yellowfive@57 | 57 | 
| yellowfive@57 | 58 	if t=="string" then		-- ^S = string (escaped to remove nonprints, "^"s, etc) | 
| yellowfive@57 | 59 		res[nres+1] = "^S" | 
| yellowfive@57 | 60 		res[nres+2] = gsub(v,"[%c \94\126\127]", SerializeStringHelper) | 
| yellowfive@57 | 61 		nres=nres+2 | 
| yellowfive@57 | 62 | 
| yellowfive@57 | 63 	elseif t=="number" then	-- ^N = number (just tostring()ed) or ^F (float components) | 
| yellowfive@57 | 64 		local str = tostring(v) | 
| yellowfive@57 | 65 		if tonumber(str)==v  --[[not in 4.3 or str==serNaN]] or str==serInf or str==serNegInf then | 
| yellowfive@57 | 66 			-- translates just fine, transmit as-is | 
| yellowfive@57 | 67 			res[nres+1] = "^N" | 
| yellowfive@57 | 68 			res[nres+2] = str | 
| yellowfive@57 | 69 			nres=nres+2 | 
| yellowfive@57 | 70 		else | 
| yellowfive@57 | 71 			local m,e = frexp(v) | 
| yellowfive@57 | 72 			res[nres+1] = "^F" | 
| yellowfive@57 | 73 			res[nres+2] = format("%.0f",m*2^53)	-- force mantissa to become integer (it's originally 0.5--0.9999) | 
| yellowfive@57 | 74 			res[nres+3] = "^f" | 
| yellowfive@57 | 75 			res[nres+4] = tostring(e-53)	-- adjust exponent to counteract mantissa manipulation | 
| yellowfive@57 | 76 			nres=nres+4 | 
| yellowfive@57 | 77 		end | 
| yellowfive@57 | 78 | 
| yellowfive@57 | 79 	elseif t=="table" then	-- ^T...^t = table (list of key,value pairs) | 
| yellowfive@57 | 80 		nres=nres+1 | 
| yellowfive@57 | 81 		res[nres] = "^T" | 
| yellowfive@57 | 82 		for k,v in pairs(v) do | 
| yellowfive@57 | 83 			nres = SerializeValue(k, res, nres) | 
| yellowfive@57 | 84 			nres = SerializeValue(v, res, nres) | 
| yellowfive@57 | 85 		end | 
| yellowfive@57 | 86 		nres=nres+1 | 
| yellowfive@57 | 87 		res[nres] = "^t" | 
| yellowfive@57 | 88 | 
| yellowfive@57 | 89 	elseif t=="boolean" then	-- ^B = true, ^b = false | 
| yellowfive@57 | 90 		nres=nres+1 | 
| yellowfive@57 | 91 		if v then | 
| yellowfive@57 | 92 			res[nres] = "^B"	-- true | 
| yellowfive@57 | 93 		else | 
| yellowfive@57 | 94 			res[nres] = "^b"	-- false | 
| yellowfive@57 | 95 		end | 
| yellowfive@57 | 96 | 
| yellowfive@57 | 97 	elseif t=="nil" then		-- ^Z = nil (zero, "N" was taken :P) | 
| yellowfive@57 | 98 		nres=nres+1 | 
| yellowfive@57 | 99 		res[nres] = "^Z" | 
| yellowfive@57 | 100 | 
| yellowfive@57 | 101 	else | 
| yellowfive@57 | 102 		error(MAJOR..": Cannot serialize a value of type '"..t.."'")	-- can't produce error on right level, this is wildly recursive | 
| yellowfive@57 | 103 	end | 
| yellowfive@57 | 104 | 
| yellowfive@57 | 105 	return nres | 
| yellowfive@57 | 106 end | 
| yellowfive@57 | 107 | 
| yellowfive@57 | 108 | 
| yellowfive@57 | 109 | 
| yellowfive@57 | 110 local serializeTbl = { "^1" }	-- "^1" = Hi, I'm data serialized by AceSerializer protocol rev 1 | 
| yellowfive@57 | 111 | 
| yellowfive@57 | 112 --- Serialize the data passed into the function. | 
| yellowfive@57 | 113 -- Takes a list of values (strings, numbers, booleans, nils, tables) | 
| yellowfive@57 | 114 -- and returns it in serialized form (a string).\\ | 
| yellowfive@57 | 115 -- May throw errors on invalid data types. | 
| yellowfive@57 | 116 -- @param ... List of values to serialize | 
| yellowfive@57 | 117 -- @return The data in its serialized form (string) | 
| yellowfive@57 | 118 function AceSerializer:Serialize(...) | 
| yellowfive@57 | 119 	local nres = 1 | 
| yellowfive@57 | 120 | 
| yellowfive@57 | 121 	for i=1,select("#", ...) do | 
| yellowfive@57 | 122 		local v = select(i, ...) | 
| yellowfive@57 | 123 		nres = SerializeValue(v, serializeTbl, nres) | 
| yellowfive@57 | 124 	end | 
| yellowfive@57 | 125 | 
| yellowfive@57 | 126 	serializeTbl[nres+1] = "^^"	-- "^^" = End of serialized data | 
| yellowfive@57 | 127 | 
| yellowfive@57 | 128 	return tconcat(serializeTbl, "", 1, nres+1) | 
| yellowfive@57 | 129 end | 
| yellowfive@57 | 130 | 
| yellowfive@57 | 131 -- Deserialization functions | 
| yellowfive@57 | 132 local function DeserializeStringHelper(escape) | 
| yellowfive@57 | 133 	if escape<"~\122" then | 
| yellowfive@57 | 134 		return strchar(strbyte(escape,2,2)-64) | 
| yellowfive@57 | 135 	elseif escape=="~\122" then	-- v3 / ticket 115: special case encode since 30+64=94 ("^") - OOPS. | 
| yellowfive@57 | 136 		return "\030" | 
| yellowfive@57 | 137 	elseif escape=="~\123" then | 
| yellowfive@57 | 138 		return "\127" | 
| yellowfive@57 | 139 	elseif escape=="~\124" then | 
| yellowfive@57 | 140 		return "\126" | 
| yellowfive@57 | 141 	elseif escape=="~\125" then | 
| yellowfive@57 | 142 		return "\94" | 
| yellowfive@57 | 143 	end | 
| yellowfive@57 | 144 	error("DeserializeStringHelper got called for '"..escape.."'?!?")  -- can't be reached unless regex is screwed up | 
| yellowfive@57 | 145 end | 
| yellowfive@57 | 146 | 
| yellowfive@57 | 147 local function DeserializeNumberHelper(number) | 
| yellowfive@57 | 148 	--[[ not in 4.3 if number == serNaN then | 
| yellowfive@57 | 149 		return 0/0 | 
| yellowfive@57 | 150 	else]]if number == serNegInf then | 
| yellowfive@57 | 151 		return -inf | 
| yellowfive@57 | 152 	elseif number == serInf then | 
| yellowfive@57 | 153 		return inf | 
| yellowfive@57 | 154 	else | 
| yellowfive@57 | 155 		return tonumber(number) | 
| yellowfive@57 | 156 	end | 
| yellowfive@57 | 157 end | 
| yellowfive@57 | 158 | 
| yellowfive@57 | 159 -- DeserializeValue: worker function for :Deserialize() | 
| yellowfive@57 | 160 -- It works in two modes: | 
| yellowfive@57 | 161 --   Main (top-level) mode: Deserialize a list of values and return them all | 
| yellowfive@57 | 162 --   Recursive (table) mode: Deserialize only a single value (_may_ of course be another table with lots of subvalues in it) | 
| yellowfive@57 | 163 -- | 
| yellowfive@57 | 164 -- The function _always_ works recursively due to having to build a list of values to return | 
| yellowfive@57 | 165 -- | 
| yellowfive@57 | 166 -- Callers are expected to pcall(DeserializeValue) to trap errors | 
| yellowfive@57 | 167 | 
| yellowfive@57 | 168 local function DeserializeValue(iter,single,ctl,data) | 
| yellowfive@57 | 169 | 
| yellowfive@57 | 170 	if not single then | 
| yellowfive@57 | 171 		ctl,data = iter() | 
| yellowfive@57 | 172 	end | 
| yellowfive@57 | 173 | 
| yellowfive@57 | 174 	if not ctl then | 
| yellowfive@57 | 175 		error("Supplied data misses AceSerializer terminator ('^^')") | 
| yellowfive@57 | 176 	end | 
| yellowfive@57 | 177 | 
| yellowfive@57 | 178 	if ctl=="^^" then | 
| yellowfive@57 | 179 		-- ignore extraneous data | 
| yellowfive@57 | 180 		return | 
| yellowfive@57 | 181 	end | 
| yellowfive@57 | 182 | 
| yellowfive@57 | 183 	local res | 
| yellowfive@57 | 184 | 
| yellowfive@57 | 185 	if ctl=="^S" then | 
| yellowfive@57 | 186 		res = gsub(data, "~.", DeserializeStringHelper) | 
| yellowfive@57 | 187 	elseif ctl=="^N" then | 
| yellowfive@57 | 188 		res = DeserializeNumberHelper(data) | 
| yellowfive@57 | 189 		if not res then | 
| yellowfive@57 | 190 			error("Invalid serialized number: '"..tostring(data).."'") | 
| yellowfive@57 | 191 		end | 
| yellowfive@57 | 192 	elseif ctl=="^F" then     -- ^F<mantissa>^f<exponent> | 
| yellowfive@57 | 193 		local ctl2,e = iter() | 
| yellowfive@57 | 194 		if ctl2~="^f" then | 
| yellowfive@57 | 195 			error("Invalid serialized floating-point number, expected '^f', not '"..tostring(ctl2).."'") | 
| yellowfive@57 | 196 		end | 
| yellowfive@57 | 197 		local m=tonumber(data) | 
| yellowfive@57 | 198 		e=tonumber(e) | 
| yellowfive@57 | 199 		if not (m and e) then | 
| yellowfive@57 | 200 			error("Invalid serialized floating-point number, expected mantissa and exponent, got '"..tostring(m).."' and '"..tostring(e).."'") | 
| yellowfive@57 | 201 		end | 
| yellowfive@57 | 202 		res = m*(2^e) | 
| yellowfive@57 | 203 	elseif ctl=="^B" then	-- yeah yeah ignore data portion | 
| yellowfive@57 | 204 		res = true | 
| yellowfive@57 | 205 	elseif ctl=="^b" then   -- yeah yeah ignore data portion | 
| yellowfive@57 | 206 		res = false | 
| yellowfive@57 | 207 	elseif ctl=="^Z" then	-- yeah yeah ignore data portion | 
| yellowfive@57 | 208 		res = nil | 
| yellowfive@57 | 209 	elseif ctl=="^T" then | 
| yellowfive@57 | 210 		-- ignore ^T's data, future extensibility? | 
| yellowfive@57 | 211 		res = {} | 
| yellowfive@57 | 212 		local k,v | 
| yellowfive@57 | 213 		while true do | 
| yellowfive@57 | 214 			ctl,data = iter() | 
| yellowfive@57 | 215 			if ctl=="^t" then break end	-- ignore ^t's data | 
| yellowfive@57 | 216 			k = DeserializeValue(iter,true,ctl,data) | 
| yellowfive@57 | 217 			if k==nil then | 
| yellowfive@57 | 218 				error("Invalid AceSerializer table format (no table end marker)") | 
| yellowfive@57 | 219 			end | 
| yellowfive@57 | 220 			ctl,data = iter() | 
| yellowfive@57 | 221 			v = DeserializeValue(iter,true,ctl,data) | 
| yellowfive@57 | 222 			if v==nil then | 
| yellowfive@57 | 223 				error("Invalid AceSerializer table format (no table end marker)") | 
| yellowfive@57 | 224 			end | 
| yellowfive@57 | 225 			res[k]=v | 
| yellowfive@57 | 226 		end | 
| yellowfive@57 | 227 	else | 
| yellowfive@57 | 228 		error("Invalid AceSerializer control code '"..ctl.."'") | 
| yellowfive@57 | 229 	end | 
| yellowfive@57 | 230 | 
| yellowfive@57 | 231 	if not single then | 
| yellowfive@57 | 232 		return res,DeserializeValue(iter) | 
| yellowfive@57 | 233 	else | 
| yellowfive@57 | 234 		return res | 
| yellowfive@57 | 235 	end | 
| yellowfive@57 | 236 end | 
| yellowfive@57 | 237 | 
| yellowfive@57 | 238 --- Deserializes the data into its original values. | 
| yellowfive@57 | 239 -- Accepts serialized data, ignoring all control characters and whitespace. | 
| yellowfive@57 | 240 -- @param str The serialized data (from :Serialize) | 
| yellowfive@57 | 241 -- @return true followed by a list of values, OR false followed by an error message | 
| yellowfive@57 | 242 function AceSerializer:Deserialize(str) | 
| yellowfive@57 | 243 	str = gsub(str, "[%c ]", "")	-- ignore all control characters; nice for embedding in email and stuff | 
| yellowfive@57 | 244 | 
| yellowfive@57 | 245 	local iter = gmatch(str, "(^.)([^^]*)")	-- Any ^x followed by string of non-^ | 
| yellowfive@57 | 246 	local ctl,data = iter() | 
| yellowfive@57 | 247 	if not ctl or ctl~="^1" then | 
| yellowfive@57 | 248 		-- we purposefully ignore the data portion of the start code, it can be used as an extension mechanism | 
| yellowfive@57 | 249 		return false, "Supplied data is not AceSerializer data (rev 1)" | 
| yellowfive@57 | 250 	end | 
| yellowfive@57 | 251 | 
| yellowfive@57 | 252 	return pcall(DeserializeValue, iter) | 
| yellowfive@57 | 253 end | 
| yellowfive@57 | 254 | 
| yellowfive@57 | 255 | 
| yellowfive@57 | 256 ---------------------------------------- | 
| yellowfive@57 | 257 -- Base library stuff | 
| yellowfive@57 | 258 ---------------------------------------- | 
| yellowfive@57 | 259 | 
| yellowfive@57 | 260 AceSerializer.internals = {	-- for test scripts | 
| yellowfive@57 | 261 	SerializeValue = SerializeValue, | 
| yellowfive@57 | 262 	SerializeStringHelper = SerializeStringHelper, | 
| yellowfive@57 | 263 } | 
| yellowfive@57 | 264 | 
| yellowfive@57 | 265 local mixins = { | 
| yellowfive@57 | 266 	"Serialize", | 
| yellowfive@57 | 267 	"Deserialize", | 
| yellowfive@57 | 268 } | 
| yellowfive@57 | 269 | 
| yellowfive@57 | 270 AceSerializer.embeds = AceSerializer.embeds or {} | 
| yellowfive@57 | 271 | 
| yellowfive@57 | 272 function AceSerializer:Embed(target) | 
| yellowfive@57 | 273 	for k, v in pairs(mixins) do | 
| yellowfive@57 | 274 		target[v] = self[v] | 
| yellowfive@57 | 275 	end | 
| yellowfive@57 | 276 	self.embeds[target] = true | 
| yellowfive@57 | 277 	return target | 
| yellowfive@57 | 278 end | 
| yellowfive@57 | 279 | 
| yellowfive@57 | 280 -- Update embeds | 
| yellowfive@57 | 281 for target, v in pairs(AceSerializer.embeds) do | 
| yellowfive@57 | 282 	AceSerializer:Embed(target) | 
| yellowfive@57 | 283 end |