From 9ffa39982ed0b9e614b7d32b03492f58ef782558 Mon Sep 17 00:00:00 2001 From: Pavel Labath Date: Thu, 19 May 2011 11:09:37 +0200 Subject: C++ binding for lua copied from conky (http://conky.sf.net) and relicensed. Since I am the person who wrote it in the first place there should not be a problem with licence conversion. --- src/FbTk/Luamm.cc | 468 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/FbTk/Luamm.hh | 341 +++++++++++++++++++++++++++++++++++++ src/FbTk/Makefile.am | 1 + 3 files changed, 810 insertions(+) create mode 100644 src/FbTk/Luamm.cc create mode 100644 src/FbTk/Luamm.hh diff --git a/src/FbTk/Luamm.cc b/src/FbTk/Luamm.cc new file mode 100644 index 0000000..a4a6eca --- /dev/null +++ b/src/FbTk/Luamm.cc @@ -0,0 +1,468 @@ +// luamm: C++ binding for lua +// Copyright (C) 2010 - 2011 Pavel Labath +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + + +#include + +#include "Luamm.hh" + +namespace lua { + namespace { + // keys for storing values in lua registry + const char cpp_exception_metatable[] = "lua::cpp_exception_metatable"; + const char cpp_function_metatable [] = "lua::cpp_function_metatable"; + const char lua_exception_namespace[] = "lua::lua_exception_namespace"; + const char this_cpp_object [] = "lua::this_cpp_object"; + + // converts C++ exceptions to strings, so lua can do something with them + int exception_to_string(lua_State *l) + { + std::exception_ptr *ptr = static_cast(lua_touserdata(l, -1)); + assert(ptr); + try { + std::rethrow_exception(*ptr); + } + catch(std::exception &e) { + lua_pushstring(l, e.what()); + } + catch(...) { + lua_pushstring(l, ptr->__cxa_exception_type()->name()); + } + return 1; + } + + int absindex(lua_State *l, int index) throw() + { return index<0 && -index<=lua_gettop(l) ? lua_gettop(l)+1+index : index; } + + // Just like getfield(), only without calling metamethods (or throwing random exceptions) + inline void rawgetfield(lua_State *l, int index, const char *k) throw(std::bad_alloc) + { + index = absindex(l, index); + if(not lua_checkstack(l, 1)) + throw std::bad_alloc(); + + lua_pushstring(l, k); + lua_rawget(l, index); + } + + // Just like setfield(), only without calling metamethods (or throwing random exceptions) + inline void rawsetfield(lua_State *l, int index, const char *k) throw(std::bad_alloc) + { + index = absindex(l, index); + if(not lua_checkstack(l, 2)) + throw std::bad_alloc(); + + lua_pushstring(l, k); + lua_insert(l, -2); + lua_rawset(l, index); + } + + int closure_trampoline(lua_State *l) + { + lua_checkstack(l, 2); + rawgetfield(l, REGISTRYINDEX, this_cpp_object); + assert(lua_islightuserdata(l, -1)); + state *L = static_cast( lua_touserdata(l, -1) ); + lua_pop(l, 1); + + try { + cpp_function *fn = static_cast( L->touserdata(lua_upvalueindex(1)) ); + assert(fn); + return (*fn)(L); + } + catch(lua::exception &e) { + // rethrow lua errors as such + e.push_lua_error(L); + } + catch(...) { + // C++ exceptions (pointers to them, actually) are stored as lua userdata and + // then thrown + L->createuserdata(std::current_exception()); + L->rawgetfield(REGISTRYINDEX, cpp_exception_metatable); + L->setmetatable(-2); + } + + // lua_error does longjmp(), so destructors for objects in this function will not be + // called + return lua_error(l); + } + + /* + * This function is called when lua encounters an error outside of any protected + * environment + * Throwing the exception through lua code appears to work, even if it was compiled + * without -fexceptions. If it turns out, it fails in some conditions, it could be + * replaced with some longjmp() magic. But that shouldn't be necessary, as this function + * will not be called under normal conditions (we execute everything in protected mode). + */ + int panic_throw(lua_State *l) + { + if(not lua_checkstack(l, 1)) + throw std::bad_alloc(); + + rawgetfield(l, REGISTRYINDEX, this_cpp_object); + assert(lua_islightuserdata(l, -1)); + state *L = static_cast( lua_touserdata(l, -1) ); + lua_pop(l, 1); + + throw lua::exception(L); + } + + // protected mode wrappers for various lua functions + int safe_concat_trampoline(lua_State *l) + { + lua_concat(l, lua_gettop(l)); + return 1; + } + + template + int safe_compare_trampoline(lua_State *l) + { + int r = compare(l, 1, 2); + lua_pop(l, 2); + lua_pushinteger(l, r); + return 1; + } + + int safe_gc_trampoline(lua_State *l) + { + int what = lua_tointeger(l, -2); + int data = lua_tointeger(l, -1); + lua_pop(l, 2); + lua_pushinteger(l, lua_gc(l, what, data)); + return 1; + } + + template + int safe_misc_trampoline(lua_State *l) + { + misc(l, 1); + return nresults; + } + + int safe_next_trampoline(lua_State *l) + { + int r = lua_next(l, 1); + lua_checkstack(l, 1); + lua_pushinteger(l, r); + return r ? 3 : 1; + } + + } + + std::string exception::get_error_msg(state *L) + { + static const std::string default_msg("Unknown lua exception"); + + try { + return L->tostring(-1); + } + catch(not_string_error &e) { + return default_msg; + } + } + + exception::exception(state *l) + : std::runtime_error(get_error_msg(l)), L(l) + { + L->checkstack(1); + + L->rawgetfield(REGISTRYINDEX, lua_exception_namespace); + L->insert(-2); + key = L->ref(-2); + L->pop(1); + } + + exception::~exception() throw() + { + if(not L) + return; + L->checkstack(1); + + L->rawgetfield(REGISTRYINDEX, lua_exception_namespace); + L->unref(-1, key); + L->pop(); + } + + void exception::push_lua_error(state *l) + { + if(l != L) + throw std::runtime_error("Cannot transfer exceptions between different lua contexts"); + l->checkstack(2); + + l->rawgetfield(REGISTRYINDEX, lua_exception_namespace); + l->rawgeti(-1, key); + l->replace(-2); + } + + state::state() + { + if(lua_State *l = luaL_newstate()) + cobj.reset(l, &lua_close); + else { + // docs say this can happen only in case of a memory allocation error + throw std::bad_alloc(); + } + + // set our panic function + lua_atpanic(cobj.get(), panic_throw); + + checkstack(2); + + // store a pointer to ourselves + pushlightuserdata(this); + rawsetfield(REGISTRYINDEX, this_cpp_object); + + // a metatable for C++ exceptions travelling through lua code + newmetatable(cpp_exception_metatable); + lua_pushcfunction(cobj.get(), &exception_to_string); + rawsetfield(-2, "__tostring"); + pushboolean(false); + rawsetfield(-2, "__metatable"); + pushdestructor(); + rawsetfield(-2, "__gc"); + pop(); + + // a metatable for C++ functions callable from lua code + newmetatable(cpp_function_metatable); + pushboolean(false); + rawsetfield(-2, "__metatable"); + pushdestructor(); + rawsetfield(-2, "__gc"); + pop(); + + // while they're travelling through C++ code, lua exceptions will reside here + newtable(); + rawsetfield(REGISTRYINDEX, lua_exception_namespace); + + luaL_openlibs(cobj.get()); + } + + void state::call(int nargs, int nresults, int errfunc) + { + int r = lua_pcall(cobj.get(), nargs, nresults, errfunc); + if(r == 0) + return; + + if(r == LUA_ERRMEM) { + // memory allocation error, cross your fingers + throw std::bad_alloc(); + } + + checkstack(3); + rawgetfield(REGISTRYINDEX, cpp_exception_metatable); + if(getmetatable(-2)) { + if(rawequal(-1, -2)) { + // it's a C++ exception, rethrow it + std::exception_ptr *ptr = static_cast(touserdata(-3)); + assert(ptr); + + /* + * we create a copy, so we can pop the object without fearing the exception will + * be collected by lua's GC + */ + std::exception_ptr t(*ptr); ptr = NULL; + pop(3); + std::rethrow_exception(t); + } + pop(2); + } + // it's a lua exception, wrap it + if(r == LUA_ERRERR) + throw lua::errfunc_error(this); + else + throw lua::exception(this); + } + + void state::checkstack(int extra) throw(std::bad_alloc) + { + if(not lua_checkstack(cobj.get(), extra)) + throw std::bad_alloc(); + } + + void state::concat(int n) + { + assert(n>=0); + checkstack(1); + lua_pushcfunction(cobj.get(), safe_concat_trampoline); + insert(-n-1); + call(n, 1, 0); + } + + bool state::equal(int index1, int index2) + { + // avoid pcall overhead in trivial cases + if( rawequal(index1, index2) ) + return true; + + return safe_compare(&safe_compare_trampoline, index1, index2); + } + + int state::gc(int what, int data) + { + checkstack(3); + lua_pushcfunction(cobj.get(), safe_gc_trampoline); + pushinteger(what); + pushinteger(data); + call(2, 1, 0); + assert(isnumber(-1)); + int r = tointeger(-1); + pop(); + return r; + } + + void state::getfield(int index, const char *k) + { + checkstack(1); + index = absindex(index); + pushstring(k); + gettable(index); + } + + void state::gettable(int index) + { + checkstack(2); + pushvalue(index); + insert(-2); + lua_pushcfunction(cobj.get(), (&safe_misc_trampoline<&lua_gettable, 1>)); + insert(-3); + call(2, 1, 0); + } + + bool state::lessthan(int index1, int index2) + { + return safe_compare(&safe_compare_trampoline<&lua_lessthan>, index1, index2); + } + + void state::loadfile(const char *filename) + throw(lua::syntax_error, lua::file_error, std::bad_alloc) + { + switch(luaL_loadfile(cobj.get(), filename)) { + case 0: + return; + case LUA_ERRSYNTAX: + throw lua::syntax_error(this); + case LUA_ERRFILE: + throw lua::file_error(this); + case LUA_ERRMEM: + throw std::bad_alloc(); + default: + assert(0); + } + } + + void state::loadstring(const char *s) throw(lua::syntax_error, std::bad_alloc) + { + switch(luaL_loadstring(cobj.get(), s)) { + case 0: + return; + case LUA_ERRSYNTAX: + throw lua::syntax_error(this); + case LUA_ERRMEM: + throw std::bad_alloc(); + default: + assert(0); + } + } + + bool state::next(int index) + { + checkstack(2); + pushvalue(index); + insert(-2); + lua_pushcfunction(cobj.get(), &safe_next_trampoline); + insert(-3); + + call(2, MULTRET, 0); + + assert(isnumber(-1)); + int r = tointeger(-1); + pop(); + return r; + } + + void state::pushclosure(const cpp_function &fn, int n) + { + checkstack(2); + + createuserdata(fn); + rawgetfield(REGISTRYINDEX, cpp_function_metatable); + setmetatable(-2); + + insert(-n-1); + lua_pushcclosure(cobj.get(), &closure_trampoline, n+1); + } + + void state::rawgetfield(int index, const char *k) throw(std::bad_alloc) + { lua::rawgetfield(cobj.get(), index, k); } + + void state::rawsetfield(int index, const char *k) throw(std::bad_alloc) + { lua::rawsetfield(cobj.get(), index, k); } + + bool state::safe_compare(lua_CFunction trampoline, int index1, int index2) + { + // if one of the indexes is invalid, return false + if(isnone(index1) || isnone(index2)) + return false; + + // convert relative indexes into absolute + index1 = absindex(index1); + index2 = absindex(index2); + + checkstack(3); + lua_pushcfunction(cobj.get(), trampoline); + pushvalue(index1); + pushvalue(index2); + call(2, 1, 0); + assert(isnumber(-1)); + int r = tointeger(-1); + pop(); + return r; + } + + void state::setfield(int index, const char *k) + { + checkstack(1); + index = absindex(index); + pushstring(k); + insert(-2); + settable(index); + } + + void state::settable(int index) + { + checkstack(2); + pushvalue(index); + insert(-3); + lua_pushcfunction(cobj.get(), (&safe_misc_trampoline<&lua_settable, 0>)); + insert(-4); + call(3, 0, 0); + } + + std::string state::tostring(int index) throw(lua::not_string_error) + { + size_t len; + const char *str = lua_tolstring(cobj.get(), index, &len); + if(not str) + throw not_string_error(); + return std::string(str, len); + } +} 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 @@ +// luamm: C++ binding for lua +// Copyright (C) 2010 - 2011 Pavel Labath +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#ifndef FBTK_LUAMM_HH +#define FBTK_LUAMM_HH + +#include +#include +#include + +#include + +namespace lua { + class state; + + typedef lua_Integer integer; + typedef lua_Number number; + typedef std::function cpp_function; + + enum { + ENVIRONINDEX = LUA_ENVIRONINDEX, + GLOBALSINDEX = LUA_GLOBALSINDEX, + REGISTRYINDEX = LUA_REGISTRYINDEX + }; + + enum { + GCSTOP = LUA_GCSTOP, + GCRESTART = LUA_GCRESTART, + GCCOLLECT = LUA_GCCOLLECT, + GCCOUNT = LUA_GCCOUNT, + GCCOUNTB = LUA_GCCOUNTB, + GCSTEP = LUA_GCSTEP, + GCSETPAUSE = LUA_GCSETPAUSE, + GCSETSTEPMUL = LUA_GCSETSTEPMUL + }; + + enum { + MULTRET = LUA_MULTRET + }; + + enum Type { + TBOOLEAN = LUA_TBOOLEAN, + TFUNCTION = LUA_TFUNCTION, + TLIGHTUSERDATA = LUA_TLIGHTUSERDATA, + TNIL = LUA_TNIL, + TNONE = LUA_TNONE, + TNUMBER = LUA_TNUMBER, + TSTRING = LUA_TSTRING, + TTABLE = LUA_TTABLE, + TTHREAD = LUA_TTHREAD, + TUSERDATA = LUA_TUSERDATA + }; + + // we reserve one upvalue for the function pointer + inline int upvalueindex(int n) + { return lua_upvalueindex(n+1); } + + /* + * Lua error()s are wrapped in this class when rethrown into C++ code. what() returns the + * error message. push_lua_error() pushes the error onto lua stack. The error can only be + * pushed into the same state it was generated in. + */ + class exception: public std::runtime_error { + /* + * We only allow moving, to avoid complications with multiple references. It shouldn't be + * difficult to modify this to work with copying, if that proves unavoidable. + */ + state *L; + int key; + + static std::string get_error_msg(state *L); + + exception(const exception &) = delete; + const exception& operator=(const exception &) = delete; + + public: + exception(exception &&other) + : std::runtime_error(std::move(other)), L(other.L), key(other.key) + { other.L = NULL; } + + explicit exception(state *l); + virtual ~exception() throw(); + + void push_lua_error(state *l); + }; + + class not_string_error: public std::runtime_error { + public: + not_string_error() + : std::runtime_error("Cannot convert value to a string") + {} + }; + + // the name says it all + class syntax_error: public lua::exception { + syntax_error(const syntax_error &) = delete; + const syntax_error& operator=(const syntax_error &) = delete; + + public: + syntax_error(state *L) + : lua::exception(L) + {} + + syntax_error(syntax_error &&other) + : lua::exception(std::move(other)) + {} + }; + + // loadfile() encountered an error while opening/reading the file + class file_error: public lua::exception { + file_error(const file_error &) = delete; + const file_error& operator=(const file_error &) = delete; + + public: + file_error(state *L) + : lua::exception(L) + {} + + file_error(file_error &&other) + : lua::exception(std::move(other)) + {} + }; + + // double fault, lua encountered an error while running the error handler function + class errfunc_error: public lua::exception { + errfunc_error(const errfunc_error &) = delete; + const errfunc_error& operator=(const errfunc_error &) = delete; + + public: + errfunc_error(state *L) + : lua::exception(L) + {} + + errfunc_error(errfunc_error &&other) + : lua::exception(std::move(other)) + {} + }; + + // a fancy wrapper around lua_State + class state { + std::shared_ptr cobj; + + // destructor for C++ objects stored as lua userdata + template + static int destroy_cpp_object(lua_State *l) + { + T *ptr = static_cast(lua_touserdata(l, -1)); + assert(ptr); + try { + // throwing exceptions in destructors is a bad idea + // but we catch (and ignore) them, just in case + ptr->~T(); + } + catch(...) { + } + return 0; + } + + bool safe_compare(lua_CFunction trampoline, int index1, int index2); + public: + state(); + + /* + * Lua functions come in three flavours + * a) functions that never throw an exception + * b) functions that throw only in case of a memory allocation error + * c) functions that throw other kinds of errors + * + * Calls to type a functions are simply forwarded to the C api. + * Type c functions are executed in protected mode, to make sure they don't longjmp() + * over us (and our destructors). This add a certain amount overhead. If you care about + * performance, try using the raw versions (if possible). + * Type b functions are not executed in protected mode atm. as memory allocation errors + * don't happen that often (as opposed to the type c, where the user get deliberately set + * a metamethod that throws an error). That means those errors will do something + * undefined, but hopefully that won't be a problem. + * + * Semantics are mostly identical to those of the underlying C api. Any deviation is + * noted in the respective functions comment. The most important difference is that + * instead of return values, we use exceptions to indicate errors. The lua and C++ + * exception mechanisms are integrated. That means one can throw a C++ exception and + * catch it in lua (with pcall). Lua error()s can be caught in C++ as exceptions of type + * lua::exception. + */ + + // type a, never throw + int absindex(int index) throw() { return index<0 && -index<=gettop() ? gettop()+1+index : index; } + bool getmetatable(int index) throw() { return lua_getmetatable(cobj.get(), index); } + int gettop() throw() { return lua_gettop(cobj.get()); } + void insert(int index) throw() { lua_insert(cobj.get(), index); } + bool isboolean(int index) throw() { return lua_isboolean(cobj.get(), index); } + bool isfunction(int index) throw() { return lua_isfunction(cobj.get(), index); } + bool islightuserdata(int index) throw() { return lua_islightuserdata(cobj.get(), index); } + bool isnil(int index) throw() { return lua_isnil(cobj.get(), index); } + bool isnone(int index) throw() { return lua_isnone(cobj.get(), index); } + bool isnumber(int index) throw() { return lua_isnumber(cobj.get(), index); } + bool isstring(int index) throw() { return lua_isstring(cobj.get(), index); } + void pop(int n = 1) throw() { lua_pop(cobj.get(), n); } + void pushboolean(bool b) throw() { lua_pushboolean(cobj.get(), b); } + void pushinteger(integer n) throw() { lua_pushinteger(cobj.get(), n); } + void pushlightuserdata(void *p) throw() { lua_pushlightuserdata(cobj.get(), p); } + void pushnil() throw() { lua_pushnil(cobj.get()); } + void pushnumber(number n) throw() { lua_pushnumber(cobj.get(), n); } + void pushvalue(int index) throw() { lua_pushvalue(cobj.get(), index); } + void rawget(int index) throw() { lua_rawget(cobj.get(), index); } + void rawgeti(int index, int n) throw() { lua_rawgeti(cobj.get(), index, n); } + bool rawequal(int index1, int index2) throw() { return lua_rawequal(cobj.get(), index1, index2); } + void replace(int index) throw() { lua_replace(cobj.get(), index); } + // lua_setmetatable returns int, but docs don't specify it's meaning :/ + int setmetatable(int index) throw() { return lua_setmetatable(cobj.get(), index); } + void settop(int index) throw() { return lua_settop(cobj.get(), index); } + bool toboolean(int index) throw() { return lua_toboolean(cobj.get(), index); } + integer tointeger(int index) throw() { return lua_tointeger(cobj.get(), index); } + number tonumber(int index) throw() { return lua_tonumber(cobj.get(), index); } + void* touserdata(int index) throw() { return lua_touserdata(cobj.get(), index); } + Type type(int index) throw() { return static_cast(lua_type(cobj.get(), index)); } + // typename is a reserved word :/ + const char* type_name(Type tp) throw() { return lua_typename(cobj.get(), tp); } + void unref(int t, int ref) throw() { return luaL_unref(cobj.get(), t, ref); } + + // type b, throw only on memory allocation errors + // checkstack correctly throws bad_alloc, because lua_checkstack kindly informs us of + // that sitution + void checkstack(int extra) throw(std::bad_alloc); + const char* gsub(const char *s, const char *p, const char *r) { return luaL_gsub(cobj.get(), s, p, r); } + bool newmetatable(const char *tname) { return luaL_newmetatable(cobj.get(), tname); } + void newtable() { lua_newtable(cobj.get()); } + void *newuserdata(size_t size) { return lua_newuserdata(cobj.get(), size); } + // cpp_function can be anything that std::function can handle, everything else remains + // identical + void pushclosure(const cpp_function &fn, int n); + void pushfunction(const cpp_function &fn) { pushclosure(fn, 0); } + void pushstring(const char *s) { lua_pushstring(cobj.get(), s); } + void pushstring(const char *s, size_t len) { lua_pushlstring(cobj.get(), s, len); } + void pushstring(const std::string &s) { lua_pushlstring(cobj.get(), s.c_str(), s.size()); } + void rawgetfield(int index, const char *k) throw(std::bad_alloc); + void rawset(int index) { lua_rawset(cobj.get(), index); } + void rawsetfield(int index, const char *k) throw(std::bad_alloc); + int ref(int t) { return luaL_ref(cobj.get(), t); } + // len recieves length, if not null. Returned value may contain '\0' + const char* tocstring(int index, size_t *len = NULL) { return lua_tolstring(cobj.get(), index, len); } + // Don't use pushclosure() to create a __gc function. The problem is that lua calls them + // in an unspecified order, and we may end up destroying the object holding the + // std::function before we get a chance to call it. This pushes a function that simply + // calls ~T when the time comes. Only set it as __gc on userdata of type T. + template + void pushdestructor() + { lua_pushcfunction(cobj.get(), &destroy_cpp_object); } + + // type c, throw everything but the kitchen sink + // call() is a protected mode call, we don't allow unprotected calls + void call(int nargs, int nresults, int errfunc = 0); + void concat(int n); + bool equal(int index1, int index2); + int gc(int what, int data); + void getfield(int index, const char *k); + void gettable(int index); + void getglobal(const char *name) { getfield(GLOBALSINDEX, name); } + bool lessthan(int index1, int index2); + void loadfile(const char *filename) throw(lua::syntax_error, lua::file_error, std::bad_alloc); + void loadstring(const char *s) throw(lua::syntax_error, std::bad_alloc); + bool next(int index); + // register is a reserved word :/ + void register_fn(const char *name, const cpp_function &f) { pushfunction(f); setglobal(name); } + void setfield(int index, const char *k); + void setglobal(const char *name) { setfield(GLOBALSINDEX, name); } + void settable(int index); + // lua_tostring uses NULL to indicate conversion error, since there is no such thing as a + // NULL std::string, we throw an exception. Returned value may contain '\0' + std::string tostring(int index) throw(lua::not_string_error); + // allocate a new lua userdata of appropriate size, and create a object in it + // pushes the userdata on stack and returns the pointer + template + T* createuserdata(Args&&... args); + }; + + /* + * Can be used to automatically pop temporary values off the lua stack on exit from the + * function/block (e.g. via an exception). It's destructor makes sure the stack contains + * exactly n items. The constructor initializes n to l.gettop()+n_, but that can be later + * changed with the overloaded operators. It is an error if stack contains less than n + * elements at entry into the destructor. + * + * Proposed stack discipline for functions is this: + * - called function always pops parameters off the stack. + * - if functions returns normally, it's return values are on the stack. + * - if function throws an exception, there are no return values on the stack. + * The last point differs from lua C api, which return an error message on the stack. But + * since we have exception.what() for that, putting the message on the stack is not + * necessary. + */ + class stack_sentry { + state *L; + int n; + + stack_sentry(const stack_sentry &) = delete; + const stack_sentry& operator=(const stack_sentry &) = delete; + public: + explicit stack_sentry(state &l, int n_ = 0) throw() + : L(&l), n(l.gettop()+n_) + { assert(n >= 0); } + + ~stack_sentry() throw() { assert(L->gettop() >= n); L->settop(n); } + + void operator++() throw() { ++n; } + void operator--() throw() { --n; assert(n >= 0); } + void operator+=(int n_) throw() { n+=n_; } + void operator-=(int n_) throw() { n-=n_; assert(n >= 0); } + }; + + template + T* state::createuserdata(Args&&... args) + { + stack_sentry s(*this); + + void *t = newuserdata(sizeof(T)); + new(t) T(std::forward(args)...); + ++s; + return static_cast(t); + } +} + +#endif // FBTK_LUAMM_HH diff --git a/src/FbTk/Makefile.am b/src/FbTk/Makefile.am index 3902552..e4101e3 100644 --- a/src/FbTk/Makefile.am +++ b/src/FbTk/Makefile.am @@ -69,6 +69,7 @@ libFbTk_a_SOURCES = App.hh App.cc \ Slot.hh Signal.hh MemFun.hh SelectArg.hh \ Util.hh \ RelCalcHelper.hh RelCalcHelper.cc \ + Luamm.cc Luamm.hh \ ${xpm_SOURCE} \ ${xft_SOURCE} \ ${xmb_SOURCE} \ -- cgit v0.11.2