diff options
Diffstat (limited to 'src/FbTk/Luamm.hh')
-rw-r--r-- | src/FbTk/Luamm.hh | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/src/FbTk/Luamm.hh b/src/FbTk/Luamm.hh new file mode 100644 index 0000000..1df5825 --- /dev/null +++ b/src/FbTk/Luamm.hh | |||
@@ -0,0 +1,341 @@ | |||
1 | // luamm: C++ binding for lua | ||
2 | // Copyright (C) 2010 - 2011 Pavel Labath | ||
3 | // | ||
4 | // Permission is hereby granted, free of charge, to any person obtaining a | ||
5 | // copy of this software and associated documentation files (the "Software"), | ||
6 | // to deal in the Software without restriction, including without limitation | ||
7 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
8 | // and/or sell copies of the Software, and to permit persons to whom the | ||
9 | // Software is furnished to do so, subject to the following conditions: | ||
10 | // | ||
11 | // The above copyright notice and this permission notice shall be included in | ||
12 | // all copies or substantial portions of the Software. | ||
13 | // | ||
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | ||
17 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
20 | // DEALINGS IN THE SOFTWARE. | ||
21 | |||
22 | #ifndef FBTK_LUAMM_HH | ||
23 | #define FBTK_LUAMM_HH | ||
24 | |||
25 | #include <assert.h> | ||
26 | #include <memory> | ||
27 | #include <stdexcept> | ||
28 | |||
29 | #include <lua.h> | ||
30 | |||
31 | namespace lua { | ||
32 | class state; | ||
33 | |||
34 | typedef lua_Integer integer; | ||
35 | typedef lua_Number number; | ||
36 | typedef std::function<int(state *)> cpp_function; | ||
37 | |||
38 | enum { | ||
39 | ENVIRONINDEX = LUA_ENVIRONINDEX, | ||
40 | GLOBALSINDEX = LUA_GLOBALSINDEX, | ||
41 | REGISTRYINDEX = LUA_REGISTRYINDEX | ||
42 | }; | ||
43 | |||
44 | enum { | ||
45 | GCSTOP = LUA_GCSTOP, | ||
46 | GCRESTART = LUA_GCRESTART, | ||
47 | GCCOLLECT = LUA_GCCOLLECT, | ||
48 | GCCOUNT = LUA_GCCOUNT, | ||
49 | GCCOUNTB = LUA_GCCOUNTB, | ||
50 | GCSTEP = LUA_GCSTEP, | ||
51 | GCSETPAUSE = LUA_GCSETPAUSE, | ||
52 | GCSETSTEPMUL = LUA_GCSETSTEPMUL | ||
53 | }; | ||
54 | |||
55 | enum { | ||
56 | MULTRET = LUA_MULTRET | ||
57 | }; | ||
58 | |||
59 | enum Type { | ||
60 | TBOOLEAN = LUA_TBOOLEAN, | ||
61 | TFUNCTION = LUA_TFUNCTION, | ||
62 | TLIGHTUSERDATA = LUA_TLIGHTUSERDATA, | ||
63 | TNIL = LUA_TNIL, | ||
64 | TNONE = LUA_TNONE, | ||
65 | TNUMBER = LUA_TNUMBER, | ||
66 | TSTRING = LUA_TSTRING, | ||
67 | TTABLE = LUA_TTABLE, | ||
68 | TTHREAD = LUA_TTHREAD, | ||
69 | TUSERDATA = LUA_TUSERDATA | ||
70 | }; | ||
71 | |||
72 | // we reserve one upvalue for the function pointer | ||
73 | inline int upvalueindex(int n) | ||
74 | { return lua_upvalueindex(n+1); } | ||
75 | |||
76 | /* | ||
77 | * Lua error()s are wrapped in this class when rethrown into C++ code. what() returns the | ||
78 | * error message. push_lua_error() pushes the error onto lua stack. The error can only be | ||
79 | * pushed into the same state it was generated in. | ||
80 | */ | ||
81 | class exception: public std::runtime_error { | ||
82 | /* | ||
83 | * We only allow moving, to avoid complications with multiple references. It shouldn't be | ||
84 | * difficult to modify this to work with copying, if that proves unavoidable. | ||
85 | */ | ||
86 | state *L; | ||
87 | int key; | ||
88 | |||
89 | static std::string get_error_msg(state *L); | ||
90 | |||
91 | exception(const exception &) = delete; | ||
92 | const exception& operator=(const exception &) = delete; | ||
93 | |||
94 | public: | ||
95 | exception(exception &&other) | ||
96 | : std::runtime_error(std::move(other)), L(other.L), key(other.key) | ||
97 | { other.L = NULL; } | ||
98 | |||
99 | explicit exception(state *l); | ||
100 | virtual ~exception() throw(); | ||
101 | |||
102 | void push_lua_error(state *l); | ||
103 | }; | ||
104 | |||
105 | class not_string_error: public std::runtime_error { | ||
106 | public: | ||
107 | not_string_error() | ||
108 | : std::runtime_error("Cannot convert value to a string") | ||
109 | {} | ||
110 | }; | ||
111 | |||
112 | // the name says it all | ||
113 | class syntax_error: public lua::exception { | ||
114 | syntax_error(const syntax_error &) = delete; | ||
115 | const syntax_error& operator=(const syntax_error &) = delete; | ||
116 | |||
117 | public: | ||
118 | syntax_error(state *L) | ||
119 | : lua::exception(L) | ||
120 | {} | ||
121 | |||
122 | syntax_error(syntax_error &&other) | ||
123 | : lua::exception(std::move(other)) | ||
124 | {} | ||
125 | }; | ||
126 | |||
127 | // loadfile() encountered an error while opening/reading the file | ||
128 | class file_error: public lua::exception { | ||
129 | file_error(const file_error &) = delete; | ||
130 | const file_error& operator=(const file_error &) = delete; | ||
131 | |||
132 | public: | ||
133 | file_error(state *L) | ||
134 | : lua::exception(L) | ||
135 | {} | ||
136 | |||
137 | file_error(file_error &&other) | ||
138 | : lua::exception(std::move(other)) | ||
139 | {} | ||
140 | }; | ||
141 | |||
142 | // double fault, lua encountered an error while running the error handler function | ||
143 | class errfunc_error: public lua::exception { | ||
144 | errfunc_error(const errfunc_error &) = delete; | ||
145 | const errfunc_error& operator=(const errfunc_error &) = delete; | ||
146 | |||
147 | public: | ||
148 | errfunc_error(state *L) | ||
149 | : lua::exception(L) | ||
150 | {} | ||
151 | |||
152 | errfunc_error(errfunc_error &&other) | ||
153 | : lua::exception(std::move(other)) | ||
154 | {} | ||
155 | }; | ||
156 | |||
157 | // a fancy wrapper around lua_State | ||
158 | class state { | ||
159 | std::shared_ptr<lua_State> cobj; | ||
160 | |||
161 | // destructor for C++ objects stored as lua userdata | ||
162 | template<typename T> | ||
163 | static int destroy_cpp_object(lua_State *l) | ||
164 | { | ||
165 | T *ptr = static_cast<T *>(lua_touserdata(l, -1)); | ||
166 | assert(ptr); | ||
167 | try { | ||
168 | // throwing exceptions in destructors is a bad idea | ||
169 | // but we catch (and ignore) them, just in case | ||
170 | ptr->~T(); | ||
171 | } | ||
172 | catch(...) { | ||
173 | } | ||
174 | return 0; | ||
175 | } | ||
176 | |||
177 | bool safe_compare(lua_CFunction trampoline, int index1, int index2); | ||
178 | public: | ||
179 | state(); | ||
180 | |||
181 | /* | ||
182 | * Lua functions come in three flavours | ||
183 | * a) functions that never throw an exception | ||
184 | * b) functions that throw only in case of a memory allocation error | ||
185 | * c) functions that throw other kinds of errors | ||
186 | * | ||
187 | * Calls to type a functions are simply forwarded to the C api. | ||
188 | * Type c functions are executed in protected mode, to make sure they don't longjmp() | ||
189 | * over us (and our destructors). This add a certain amount overhead. If you care about | ||
190 | * performance, try using the raw versions (if possible). | ||
191 | * Type b functions are not executed in protected mode atm. as memory allocation errors | ||
192 | * don't happen that often (as opposed to the type c, where the user get deliberately set | ||
193 | * a metamethod that throws an error). That means those errors will do something | ||
194 | * undefined, but hopefully that won't be a problem. | ||
195 | * | ||
196 | * Semantics are mostly identical to those of the underlying C api. Any deviation is | ||
197 | * noted in the respective functions comment. The most important difference is that | ||
198 | * instead of return values, we use exceptions to indicate errors. The lua and C++ | ||
199 | * exception mechanisms are integrated. That means one can throw a C++ exception and | ||
200 | * catch it in lua (with pcall). Lua error()s can be caught in C++ as exceptions of type | ||
201 | * lua::exception. | ||
202 | */ | ||
203 | |||
204 | // type a, never throw | ||
205 | int absindex(int index) throw() { return index<0 && -index<=gettop() ? gettop()+1+index : index; } | ||
206 | bool getmetatable(int index) throw() { return lua_getmetatable(cobj.get(), index); } | ||
207 | int gettop() throw() { return lua_gettop(cobj.get()); } | ||
208 | void insert(int index) throw() { lua_insert(cobj.get(), index); } | ||
209 | bool isboolean(int index) throw() { return lua_isboolean(cobj.get(), index); } | ||
210 | bool isfunction(int index) throw() { return lua_isfunction(cobj.get(), index); } | ||
211 | bool islightuserdata(int index) throw() { return lua_islightuserdata(cobj.get(), index); } | ||
212 | bool isnil(int index) throw() { return lua_isnil(cobj.get(), index); } | ||
213 | bool isnone(int index) throw() { return lua_isnone(cobj.get(), index); } | ||
214 | bool isnumber(int index) throw() { return lua_isnumber(cobj.get(), index); } | ||
215 | bool isstring(int index) throw() { return lua_isstring(cobj.get(), index); } | ||
216 | void pop(int n = 1) throw() { lua_pop(cobj.get(), n); } | ||
217 | void pushboolean(bool b) throw() { lua_pushboolean(cobj.get(), b); } | ||
218 | void pushinteger(integer n) throw() { lua_pushinteger(cobj.get(), n); } | ||
219 | void pushlightuserdata(void *p) throw() { lua_pushlightuserdata(cobj.get(), p); } | ||
220 | void pushnil() throw() { lua_pushnil(cobj.get()); } | ||
221 | void pushnumber(number n) throw() { lua_pushnumber(cobj.get(), n); } | ||
222 | void pushvalue(int index) throw() { lua_pushvalue(cobj.get(), index); } | ||
223 | void rawget(int index) throw() { lua_rawget(cobj.get(), index); } | ||
224 | void rawgeti(int index, int n) throw() { lua_rawgeti(cobj.get(), index, n); } | ||
225 | bool rawequal(int index1, int index2) throw() { return lua_rawequal(cobj.get(), index1, index2); } | ||
226 | void replace(int index) throw() { lua_replace(cobj.get(), index); } | ||
227 | // lua_setmetatable returns int, but docs don't specify it's meaning :/ | ||
228 | int setmetatable(int index) throw() { return lua_setmetatable(cobj.get(), index); } | ||
229 | void settop(int index) throw() { return lua_settop(cobj.get(), index); } | ||
230 | bool toboolean(int index) throw() { return lua_toboolean(cobj.get(), index); } | ||
231 | integer tointeger(int index) throw() { return lua_tointeger(cobj.get(), index); } | ||
232 | number tonumber(int index) throw() { return lua_tonumber(cobj.get(), index); } | ||
233 | void* touserdata(int index) throw() { return lua_touserdata(cobj.get(), index); } | ||
234 | Type type(int index) throw() { return static_cast<Type>(lua_type(cobj.get(), index)); } | ||
235 | // typename is a reserved word :/ | ||
236 | const char* type_name(Type tp) throw() { return lua_typename(cobj.get(), tp); } | ||
237 | void unref(int t, int ref) throw() { return luaL_unref(cobj.get(), t, ref); } | ||
238 | |||
239 | // type b, throw only on memory allocation errors | ||
240 | // checkstack correctly throws bad_alloc, because lua_checkstack kindly informs us of | ||
241 | // that sitution | ||
242 | void checkstack(int extra) throw(std::bad_alloc); | ||
243 | const char* gsub(const char *s, const char *p, const char *r) { return luaL_gsub(cobj.get(), s, p, r); } | ||
244 | bool newmetatable(const char *tname) { return luaL_newmetatable(cobj.get(), tname); } | ||
245 | void newtable() { lua_newtable(cobj.get()); } | ||
246 | void *newuserdata(size_t size) { return lua_newuserdata(cobj.get(), size); } | ||
247 | // cpp_function can be anything that std::function can handle, everything else remains | ||
248 | // identical | ||
249 | void pushclosure(const cpp_function &fn, int n); | ||
250 | void pushfunction(const cpp_function &fn) { pushclosure(fn, 0); } | ||
251 | void pushstring(const char *s) { lua_pushstring(cobj.get(), s); } | ||
252 | void pushstring(const char *s, size_t len) { lua_pushlstring(cobj.get(), s, len); } | ||
253 | void pushstring(const std::string &s) { lua_pushlstring(cobj.get(), s.c_str(), s.size()); } | ||
254 | void rawgetfield(int index, const char *k) throw(std::bad_alloc); | ||
255 | void rawset(int index) { lua_rawset(cobj.get(), index); } | ||
256 | void rawsetfield(int index, const char *k) throw(std::bad_alloc); | ||
257 | int ref(int t) { return luaL_ref(cobj.get(), t); } | ||
258 | // len recieves length, if not null. Returned value may contain '\0' | ||
259 | const char* tocstring(int index, size_t *len = NULL) { return lua_tolstring(cobj.get(), index, len); } | ||
260 | // Don't use pushclosure() to create a __gc function. The problem is that lua calls them | ||
261 | // in an unspecified order, and we may end up destroying the object holding the | ||
262 | // std::function before we get a chance to call it. This pushes a function that simply | ||
263 | // calls ~T when the time comes. Only set it as __gc on userdata of type T. | ||
264 | template<typename T> | ||
265 | void pushdestructor() | ||
266 | { lua_pushcfunction(cobj.get(), &destroy_cpp_object<T>); } | ||
267 | |||
268 | // type c, throw everything but the kitchen sink | ||
269 | // call() is a protected mode call, we don't allow unprotected calls | ||
270 | void call(int nargs, int nresults, int errfunc = 0); | ||
271 | void concat(int n); | ||
272 | bool equal(int index1, int index2); | ||
273 | int gc(int what, int data); | ||
274 | void getfield(int index, const char *k); | ||
275 | void gettable(int index); | ||
276 | void getglobal(const char *name) { getfield(GLOBALSINDEX, name); } | ||
277 | bool lessthan(int index1, int index2); | ||
278 | void loadfile(const char *filename) throw(lua::syntax_error, lua::file_error, std::bad_alloc); | ||
279 | void loadstring(const char *s) throw(lua::syntax_error, std::bad_alloc); | ||
280 | bool next(int index); | ||
281 | // register is a reserved word :/ | ||
282 | void register_fn(const char *name, const cpp_function &f) { pushfunction(f); setglobal(name); } | ||
283 | void setfield(int index, const char *k); | ||
284 | void setglobal(const char *name) { setfield(GLOBALSINDEX, name); } | ||
285 | void settable(int index); | ||
286 | // lua_tostring uses NULL to indicate conversion error, since there is no such thing as a | ||
287 | // NULL std::string, we throw an exception. Returned value may contain '\0' | ||
288 | std::string tostring(int index) throw(lua::not_string_error); | ||
289 | // allocate a new lua userdata of appropriate size, and create a object in it | ||
290 | // pushes the userdata on stack and returns the pointer | ||
291 | template<typename T, typename... Args> | ||
292 | T* createuserdata(Args&&... args); | ||
293 | }; | ||
294 | |||
295 | /* | ||
296 | * Can be used to automatically pop temporary values off the lua stack on exit from the | ||
297 | * function/block (e.g. via an exception). It's destructor makes sure the stack contains | ||
298 | * exactly n items. The constructor initializes n to l.gettop()+n_, but that can be later | ||
299 | * changed with the overloaded operators. It is an error if stack contains less than n | ||
300 | * elements at entry into the destructor. | ||
301 | * | ||
302 | * Proposed stack discipline for functions is this: | ||
303 | * - called function always pops parameters off the stack. | ||
304 | * - if functions returns normally, it's return values are on the stack. | ||
305 | * - if function throws an exception, there are no return values on the stack. | ||
306 | * The last point differs from lua C api, which return an error message on the stack. But | ||
307 | * since we have exception.what() for that, putting the message on the stack is not | ||
308 | * necessary. | ||
309 | */ | ||
310 | class stack_sentry { | ||
311 | state *L; | ||
312 | int n; | ||
313 | |||
314 | stack_sentry(const stack_sentry &) = delete; | ||
315 | const stack_sentry& operator=(const stack_sentry &) = delete; | ||
316 | public: | ||
317 | explicit stack_sentry(state &l, int n_ = 0) throw() | ||
318 | : L(&l), n(l.gettop()+n_) | ||
319 | { assert(n >= 0); } | ||
320 | |||
321 | ~stack_sentry() throw() { assert(L->gettop() >= n); L->settop(n); } | ||
322 | |||
323 | void operator++() throw() { ++n; } | ||
324 | void operator--() throw() { --n; assert(n >= 0); } | ||
325 | void operator+=(int n_) throw() { n+=n_; } | ||
326 | void operator-=(int n_) throw() { n-=n_; assert(n >= 0); } | ||
327 | }; | ||
328 | |||
329 | template<typename T, typename... Args> | ||
330 | T* state::createuserdata(Args&&... args) | ||
331 | { | ||
332 | stack_sentry s(*this); | ||
333 | |||
334 | void *t = newuserdata(sizeof(T)); | ||
335 | new(t) T(std::forward<Args>(args)...); | ||
336 | ++s; | ||
337 | return static_cast<T *>(t); | ||
338 | } | ||
339 | } | ||
340 | |||
341 | #endif // FBTK_LUAMM_HH | ||