Mercurial > wow > askmrrobot
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 |