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 |