annotate Libs/AceSerializer-3.0/AceSerializer-3.0.lua @ 201:3447634f0388 tip

Added tag v97 for changeset 6e8838b231d4
author Yellowfive
date Wed, 13 Jan 2021 13:12:13 -0600
parents e635cd648e01
children
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@106 13 -- @release $Id: AceSerializer-3.0.lua 1135 2015-09-19 20:39:16Z nevcairiel $
yellowfive@106 14 local MAJOR,MINOR = "AceSerializer-3.0", 5
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@106 30 local serInf, serInfMac = "1.#INF", "inf"
yellowfive@106 31 local serNegInf, serNegInfMac = "-1.#INF", "-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@106 65 if tonumber(str)==v --[[not in 4.3 or str==serNaN]] 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@106 70 elseif v == inf or v == -inf then
yellowfive@106 71 res[nres+1] = "^N"
yellowfive@106 72 res[nres+2] = v == inf and serInf or serNegInf
yellowfive@106 73 nres=nres+2
yellowfive@57 74 else
yellowfive@57 75 local m,e = frexp(v)
yellowfive@57 76 res[nres+1] = "^F"
yellowfive@57 77 res[nres+2] = format("%.0f",m*2^53) -- force mantissa to become integer (it's originally 0.5--0.9999)
yellowfive@57 78 res[nres+3] = "^f"
yellowfive@57 79 res[nres+4] = tostring(e-53) -- adjust exponent to counteract mantissa manipulation
yellowfive@57 80 nres=nres+4
yellowfive@57 81 end
yellowfive@57 82
yellowfive@57 83 elseif t=="table" then -- ^T...^t = table (list of key,value pairs)
yellowfive@57 84 nres=nres+1
yellowfive@57 85 res[nres] = "^T"
yellowfive@57 86 for k,v in pairs(v) do
yellowfive@57 87 nres = SerializeValue(k, res, nres)
yellowfive@57 88 nres = SerializeValue(v, res, nres)
yellowfive@57 89 end
yellowfive@57 90 nres=nres+1
yellowfive@57 91 res[nres] = "^t"
yellowfive@57 92
yellowfive@57 93 elseif t=="boolean" then -- ^B = true, ^b = false
yellowfive@57 94 nres=nres+1
yellowfive@57 95 if v then
yellowfive@57 96 res[nres] = "^B" -- true
yellowfive@57 97 else
yellowfive@57 98 res[nres] = "^b" -- false
yellowfive@57 99 end
yellowfive@57 100
yellowfive@57 101 elseif t=="nil" then -- ^Z = nil (zero, "N" was taken :P)
yellowfive@57 102 nres=nres+1
yellowfive@57 103 res[nres] = "^Z"
yellowfive@57 104
yellowfive@57 105 else
yellowfive@57 106 error(MAJOR..": Cannot serialize a value of type '"..t.."'") -- can't produce error on right level, this is wildly recursive
yellowfive@57 107 end
yellowfive@57 108
yellowfive@57 109 return nres
yellowfive@57 110 end
yellowfive@57 111
yellowfive@57 112
yellowfive@57 113
yellowfive@57 114 local serializeTbl = { "^1" } -- "^1" = Hi, I'm data serialized by AceSerializer protocol rev 1
yellowfive@57 115
yellowfive@57 116 --- Serialize the data passed into the function.
yellowfive@57 117 -- Takes a list of values (strings, numbers, booleans, nils, tables)
yellowfive@57 118 -- and returns it in serialized form (a string).\\
yellowfive@57 119 -- May throw errors on invalid data types.
yellowfive@57 120 -- @param ... List of values to serialize
yellowfive@57 121 -- @return The data in its serialized form (string)
yellowfive@57 122 function AceSerializer:Serialize(...)
yellowfive@57 123 local nres = 1
yellowfive@57 124
yellowfive@57 125 for i=1,select("#", ...) do
yellowfive@57 126 local v = select(i, ...)
yellowfive@57 127 nres = SerializeValue(v, serializeTbl, nres)
yellowfive@57 128 end
yellowfive@57 129
yellowfive@57 130 serializeTbl[nres+1] = "^^" -- "^^" = End of serialized data
yellowfive@57 131
yellowfive@57 132 return tconcat(serializeTbl, "", 1, nres+1)
yellowfive@57 133 end
yellowfive@57 134
yellowfive@57 135 -- Deserialization functions
yellowfive@57 136 local function DeserializeStringHelper(escape)
yellowfive@57 137 if escape<"~\122" then
yellowfive@57 138 return strchar(strbyte(escape,2,2)-64)
yellowfive@57 139 elseif escape=="~\122" then -- v3 / ticket 115: special case encode since 30+64=94 ("^") - OOPS.
yellowfive@57 140 return "\030"
yellowfive@57 141 elseif escape=="~\123" then
yellowfive@57 142 return "\127"
yellowfive@57 143 elseif escape=="~\124" then
yellowfive@57 144 return "\126"
yellowfive@57 145 elseif escape=="~\125" then
yellowfive@57 146 return "\94"
yellowfive@57 147 end
yellowfive@57 148 error("DeserializeStringHelper got called for '"..escape.."'?!?") -- can't be reached unless regex is screwed up
yellowfive@57 149 end
yellowfive@57 150
yellowfive@57 151 local function DeserializeNumberHelper(number)
yellowfive@57 152 --[[ not in 4.3 if number == serNaN then
yellowfive@57 153 return 0/0
yellowfive@106 154 else]]if number == serNegInf or number == serNegInfMac then
yellowfive@57 155 return -inf
yellowfive@106 156 elseif number == serInf or number == serInfMac then
yellowfive@57 157 return inf
yellowfive@57 158 else
yellowfive@57 159 return tonumber(number)
yellowfive@57 160 end
yellowfive@57 161 end
yellowfive@57 162
yellowfive@57 163 -- DeserializeValue: worker function for :Deserialize()
yellowfive@57 164 -- It works in two modes:
yellowfive@57 165 -- Main (top-level) mode: Deserialize a list of values and return them all
yellowfive@57 166 -- Recursive (table) mode: Deserialize only a single value (_may_ of course be another table with lots of subvalues in it)
yellowfive@57 167 --
yellowfive@57 168 -- The function _always_ works recursively due to having to build a list of values to return
yellowfive@57 169 --
yellowfive@57 170 -- Callers are expected to pcall(DeserializeValue) to trap errors
yellowfive@57 171
yellowfive@57 172 local function DeserializeValue(iter,single,ctl,data)
yellowfive@57 173
yellowfive@57 174 if not single then
yellowfive@57 175 ctl,data = iter()
yellowfive@57 176 end
yellowfive@57 177
yellowfive@57 178 if not ctl then
yellowfive@57 179 error("Supplied data misses AceSerializer terminator ('^^')")
yellowfive@57 180 end
yellowfive@57 181
yellowfive@57 182 if ctl=="^^" then
yellowfive@57 183 -- ignore extraneous data
yellowfive@57 184 return
yellowfive@57 185 end
yellowfive@57 186
yellowfive@57 187 local res
yellowfive@57 188
yellowfive@57 189 if ctl=="^S" then
yellowfive@57 190 res = gsub(data, "~.", DeserializeStringHelper)
yellowfive@57 191 elseif ctl=="^N" then
yellowfive@57 192 res = DeserializeNumberHelper(data)
yellowfive@57 193 if not res then
yellowfive@57 194 error("Invalid serialized number: '"..tostring(data).."'")
yellowfive@57 195 end
yellowfive@57 196 elseif ctl=="^F" then -- ^F<mantissa>^f<exponent>
yellowfive@57 197 local ctl2,e = iter()
yellowfive@57 198 if ctl2~="^f" then
yellowfive@57 199 error("Invalid serialized floating-point number, expected '^f', not '"..tostring(ctl2).."'")
yellowfive@57 200 end
yellowfive@57 201 local m=tonumber(data)
yellowfive@57 202 e=tonumber(e)
yellowfive@57 203 if not (m and e) then
yellowfive@57 204 error("Invalid serialized floating-point number, expected mantissa and exponent, got '"..tostring(m).."' and '"..tostring(e).."'")
yellowfive@57 205 end
yellowfive@57 206 res = m*(2^e)
yellowfive@57 207 elseif ctl=="^B" then -- yeah yeah ignore data portion
yellowfive@57 208 res = true
yellowfive@57 209 elseif ctl=="^b" then -- yeah yeah ignore data portion
yellowfive@57 210 res = false
yellowfive@57 211 elseif ctl=="^Z" then -- yeah yeah ignore data portion
yellowfive@57 212 res = nil
yellowfive@57 213 elseif ctl=="^T" then
yellowfive@57 214 -- ignore ^T's data, future extensibility?
yellowfive@57 215 res = {}
yellowfive@57 216 local k,v
yellowfive@57 217 while true do
yellowfive@57 218 ctl,data = iter()
yellowfive@57 219 if ctl=="^t" then break end -- ignore ^t's data
yellowfive@57 220 k = DeserializeValue(iter,true,ctl,data)
yellowfive@57 221 if k==nil then
yellowfive@57 222 error("Invalid AceSerializer table format (no table end marker)")
yellowfive@57 223 end
yellowfive@57 224 ctl,data = iter()
yellowfive@57 225 v = DeserializeValue(iter,true,ctl,data)
yellowfive@57 226 if v==nil then
yellowfive@57 227 error("Invalid AceSerializer table format (no table end marker)")
yellowfive@57 228 end
yellowfive@57 229 res[k]=v
yellowfive@57 230 end
yellowfive@57 231 else
yellowfive@57 232 error("Invalid AceSerializer control code '"..ctl.."'")
yellowfive@57 233 end
yellowfive@57 234
yellowfive@57 235 if not single then
yellowfive@57 236 return res,DeserializeValue(iter)
yellowfive@57 237 else
yellowfive@57 238 return res
yellowfive@57 239 end
yellowfive@57 240 end
yellowfive@57 241
yellowfive@57 242 --- Deserializes the data into its original values.
yellowfive@57 243 -- Accepts serialized data, ignoring all control characters and whitespace.
yellowfive@57 244 -- @param str The serialized data (from :Serialize)
yellowfive@57 245 -- @return true followed by a list of values, OR false followed by an error message
yellowfive@57 246 function AceSerializer:Deserialize(str)
yellowfive@57 247 str = gsub(str, "[%c ]", "") -- ignore all control characters; nice for embedding in email and stuff
yellowfive@57 248
yellowfive@57 249 local iter = gmatch(str, "(^.)([^^]*)") -- Any ^x followed by string of non-^
yellowfive@57 250 local ctl,data = iter()
yellowfive@57 251 if not ctl or ctl~="^1" then
yellowfive@57 252 -- we purposefully ignore the data portion of the start code, it can be used as an extension mechanism
yellowfive@57 253 return false, "Supplied data is not AceSerializer data (rev 1)"
yellowfive@57 254 end
yellowfive@57 255
yellowfive@57 256 return pcall(DeserializeValue, iter)
yellowfive@57 257 end
yellowfive@57 258
yellowfive@57 259
yellowfive@57 260 ----------------------------------------
yellowfive@57 261 -- Base library stuff
yellowfive@57 262 ----------------------------------------
yellowfive@57 263
yellowfive@57 264 AceSerializer.internals = { -- for test scripts
yellowfive@57 265 SerializeValue = SerializeValue,
yellowfive@57 266 SerializeStringHelper = SerializeStringHelper,
yellowfive@57 267 }
yellowfive@57 268
yellowfive@57 269 local mixins = {
yellowfive@57 270 "Serialize",
yellowfive@57 271 "Deserialize",
yellowfive@57 272 }
yellowfive@57 273
yellowfive@57 274 AceSerializer.embeds = AceSerializer.embeds or {}
yellowfive@57 275
yellowfive@57 276 function AceSerializer:Embed(target)
yellowfive@57 277 for k, v in pairs(mixins) do
yellowfive@57 278 target[v] = self[v]
yellowfive@57 279 end
yellowfive@57 280 self.embeds[target] = true
yellowfive@57 281 return target
yellowfive@57 282 end
yellowfive@57 283
yellowfive@57 284 -- Update embeds
yellowfive@57 285 for target, v in pairs(AceSerializer.embeds) do
yellowfive@57 286 AceSerializer:Embed(target)
yellowfive@106 287 end