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