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