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