1 /*
2 LUA_SERIALIZE.CPP
3
4 Copyright (C) 2009 by Gregory Smith
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 This license is contained in the file "COPYING",
17 which is included with this source code; it is available online at
18 http://www.gnu.org/licenses/gpl.html
19
20 Serializes Lua objects
21 Based on Pluto, but far less clever
22 */
23
24 #include "lua_serialize.h"
25 #include "Logging.h"
26
27 #include "BStream.h"
28
29 const static int SAVED_REFERENCE_PSEUDOTYPE = -2;
30 const uint16 kVersion = 1;
31
valid_key(int type)32 static bool valid_key(int type)
33 {
34 return (type == LUA_TNUMBER ||
35 type == LUA_TBOOLEAN ||
36 type == LUA_TSTRING ||
37 type == LUA_TTABLE ||
38 type == LUA_TUSERDATA);
39 }
40
save(lua_State * L,BOStreamBE & s,uint32 & counter)41 static void save(lua_State *L, BOStreamBE& s, uint32& counter)
42 {
43 // if the object has already been written, write a reference to it
44
45 lua_pushvalue(L, -1);
46 lua_rawget(L, 1);
47 if (!lua_isnil(L, -1))
48 {
49 s << static_cast<int8>(SAVED_REFERENCE_PSEUDOTYPE)
50 << static_cast<uint32>(lua_tonumber(L, -1));
51 lua_pop(L, 1);
52 return;
53 }
54 lua_pop(L, 1);
55
56 s << static_cast<int8>(lua_type(L, -1));
57 switch (lua_type(L, -1))
58 {
59 case LUA_TNIL:
60 break;
61 case LUA_TNUMBER:
62 {
63 s << static_cast<double>(lua_tonumber(L, -1));
64 }
65 break;
66 case LUA_TBOOLEAN:
67 s << static_cast<uint8>(lua_toboolean(L, -1) ? 1 : 0);
68 break;
69 case LUA_TSTRING:
70 {
71 s << static_cast<uint32>(lua_rawlen(L, -1));
72 s.write(lua_tostring(L, -1), lua_rawlen(L, -1));
73 }
74 break;
75 case LUA_TTABLE:
76 {
77 // add to the reference table
78 lua_pushvalue(L, -1);
79 lua_pushnumber(L, static_cast<lua_Number>(++counter));
80 lua_rawset(L, 1);
81
82 // write the reference
83 s << counter;
84
85 // write all k/v pairs
86 lua_pushnil(L);
87 while (lua_next(L, -2))
88 {
89 if (valid_key(lua_type(L, -2))) {
90 // another key
91 lua_pushvalue(L, -2);
92
93 save(L, s, counter);
94 lua_pop(L, 1);
95
96 save(L, s, counter);
97 lua_pop(L, 1);
98 } else {
99 lua_pop(L, 1);
100 }
101 }
102
103 lua_pushnil(L);
104 save(L, s, counter);
105 lua_pop(L, 1);
106 }
107 break;
108 case LUA_TUSERDATA:
109 {
110 // add to the reference table
111 lua_pushvalue(L, -1);
112 lua_pushnumber(L, static_cast<lua_Number>(++counter));
113 lua_rawset(L, 1);
114
115 // write the reference
116 s << counter;
117
118 // assume that this is one of our userdata
119 lua_getmetatable(L, -1);
120 lua_gettable(L, LUA_REGISTRYINDEX);
121
122 s << static_cast<uint8>(lua_rawlen(L, -1));
123 s.write(lua_tostring(L, -1), lua_rawlen(L, -1));
124 lua_pop(L, 1);
125
126 lua_getfield(L, -1, "index");
127
128 s << static_cast<uint32>(lua_tonumber(L, -1));
129 lua_pop(L, 1);
130 }
131 break;
132
133 default:
134 // we silently ignore other types
135 break;
136 }
137 }
138
lua_save(lua_State * L,std::streambuf * sb)139 bool lua_save(lua_State *L, std::streambuf* sb)
140 {
141 lua_assert(lua_gettop(L) == 1);
142
143 // create a references table
144 lua_newtable(L);
145
146 // put it at the bottom of the stack
147 lua_insert(L, 1);
148
149 uint32 counter = 0;
150 BOStreamBE s(sb);
151 try
152 {
153 s << kVersion;
154 save(L, s, counter);
155 }
156 catch (const basic_bstream::failure& e)
157 {
158 logWarning("failed to save Lua data; %s", e.what());
159 lua_settop(L, 0);
160 return false;
161 }
162
163 // remove the reference table
164 lua_remove(L, 1);
165 return true;
166 }
167
restore(lua_State * L,BIStreamBE & s)168 static int restore(lua_State *L, BIStreamBE& s)
169 {
170 int8 type;
171 s >> type;
172
173 switch (type)
174 {
175 case LUA_TNIL:
176 lua_pushnil(L);
177 break;
178 case LUA_TBOOLEAN:
179 uint8 b;
180 s >> b;
181 lua_pushboolean(L, b == 1);
182 break;
183 case LUA_TNUMBER:
184 {
185 double d;
186 s >> d;
187 lua_pushnumber(L, static_cast<lua_Number>(d));
188 }
189 break;
190 case LUA_TSTRING:
191 {
192 uint32 length;
193 s >> length;
194 std::vector<char> v(length);
195 s.read(&v[0], v.size());
196 lua_pushlstring(L, &v[0], v.size());
197 }
198 break;
199 case LUA_TTABLE:
200 {
201 uint32 reference;
202 s >> reference;
203
204 // add to the reference table
205 lua_newtable(L);
206 lua_pushnumber(L, static_cast<lua_Number>(reference));
207 lua_pushvalue(L, -2);
208 lua_rawset(L, 1);
209
210 int key_type = restore(L, s);
211 while (key_type != LUA_TNIL)
212 {
213 restore(L, s); // value
214 if (lua_isnil(L, -2))
215 {
216 // maybe an invalid userdata?
217 lua_pop(L, 2);
218 }
219 else
220 {
221 lua_rawset(L, -3);
222 }
223 key_type = restore(L, s); // next key
224 }
225 lua_pop(L, 1);
226 }
227 break;
228 case LUA_TUSERDATA:
229 {
230 uint32 reference;
231 s >> reference;
232
233 uint8 length;
234 s >> length;
235 std::vector<char> v(length);
236 s.read(&v[0], v.size());
237 lua_pushlstring(L, &v[0], v.size());
238
239 uint32 index;
240 s >> index;
241
242 // get the metatable
243 lua_gettable(L, LUA_REGISTRYINDEX);
244 // get the accessor we added
245 lua_getfield(L, -1, "__new");
246 if (lua_isfunction(L, -1))
247 {
248 lua_pushnumber(L, static_cast<lua_Number>(index));
249 lua_call(L, 1, 1);
250 }
251
252 lua_remove(L, -2);
253
254 // add to the reference table
255 lua_pushnumber(L, static_cast<lua_Number>(reference));
256 lua_pushvalue(L, -2);
257 lua_rawset(L, 1);
258
259 }
260 break;
261
262 case SAVED_REFERENCE_PSEUDOTYPE:
263 {
264 uint32 index;
265 s >> index;
266 lua_pushnumber(L, static_cast<lua_Number>(index));
267 lua_rawget(L, 1);
268 }
269 break;
270 default:
271 lua_pushnil(L);
272 break;
273 }
274
275 return type;
276 }
277
lua_restore(lua_State * L,std::streambuf * sb)278 bool lua_restore(lua_State *L, std::streambuf* sb)
279 {
280 // create a reference table
281 lua_newtable(L);
282
283 // put it at the bottom of the stack
284 lua_insert(L, 1);
285
286 BIStreamBE s(sb);
287 try {
288 int16 version;
289 s >> version;
290 if (version > kVersion)
291 {
292 logWarning("failed to restore Lua data; saved data is newer version");
293 return false;
294 }
295
296 restore(L, s);
297 }
298 catch (const basic_bstream::failure& e)
299 {
300 logWarning("failed to restore Lua data; %s", e.what());
301 lua_settop(L, 0);
302 return false;
303 }
304
305 // remove the reference table
306 lua_remove(L, 1);
307 return true;
308 }
309