annotate Libs/AceSerializer-3.0/AceSerializer-3.0.lua @ 80:8f235b016212

Added tag v32 for changeset a892c863c86a
author yellowfive
date Tue, 05 Apr 2016 16:19:31 -0700
parents 01b63b8ed811
children e635cd648e01
rev   line source
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