comparison Libs/AceSerializer-3.0/AceSerializer-3.0.lua @ 57:01b63b8ed811 v21

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