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