/* SLB - Simple Lua Binder Copyright (C) 2007 Jose L. Hidalgo ValiƱo (PpluX) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Jose L. Hidalgo (www.pplux.com) pplux@pplux.com */ #include #include #include #include #include namespace SLB { /*--- Invalid Method (exception) ----------------------------------------------*/ InvalidMethod::InvalidMethod(const HybridBase *obj, const char *c) { SLB_DEBUG_CALL; const ClassInfo *CI = obj->getClassInfo(); std::ostringstream out; out << "Invalid Method '" << CI->getName() << "::" << c << "' NOT FOUND!" << std::endl; _what = out.str(); } /*--- Invalid Method (exception) ----------------------------------------------*/ /*--- HybridBase::AutoLock ----------------------------------------------------*/ HybridBase::AutoLock::AutoLock(const HybridBase *hconst) { SLB_DEBUG_CALL; //TODO: Review this! (should be const?) _hybrid = const_cast(hconst); SLB_DEBUG(6, "Lock state %p to access hybrid method (%p)", _hybrid->_L, (void*) _hybrid); _hybrid->lockBegin( _hybrid->_L ); } HybridBase::AutoLock::~AutoLock() { SLB_DEBUG_CALL; SLB_DEBUG(6, "Unlock state %p to access hybrid method (%p)", _hybrid->_L, (void*) _hybrid); _hybrid->lockEnd( _hybrid->_L); } /*--- HybridBase::AutoLock -------------------------------------*/ /*--- Internal Hybrid Subclasses ---------------------------------------*/ struct InternalHybridSubclass : public Table { InternalHybridSubclass(ClassInfo *ci) : _CI(ci) { SLB_DEBUG_CALL; assert("Invalid ClassInfo" && _CI.valid()); } int __newindex(lua_State *L) { SLB_DEBUG_CALL; SLB_DEBUG_CLEAN_STACK(L,-2); SLB_DEBUG_STACK(6,L, "Call InternalHybridSubclass(%p)::__nexindex", this); //1 = table //2 = string //3 = function luaL_checkstring(L,2); if (lua_type(L,3) != LUA_TFUNCTION) { luaL_error(L, "Only functions can be added to hybrid classes" " (invalid %s of type %s)", lua_tostring(L,2), lua_typename(L, 3)); } SLB_DEBUG(4, "Added method to an hybrid-subclass:%s", lua_tostring(L,2)); lua_pushcclosure(L, HybridBase::call_lua_method, 1); // replaces setCache(L); return 0; } int __call(lua_State *L) { SLB_DEBUG_CALL; SLB_DEBUG_STACK(6,L, "Call InternalHybridSubclass(%p)::__call", this); // create new instance: ref_ptr fc = _CI->getConstructor(); assert("Invalid Constructor!" && fc.valid()); fc->push(L); lua_replace(L,1); // table of metamethod __call lua_call(L, lua_gettop(L) -1 , LUA_MULTRET); { SLB_DEBUG_CLEAN_STACK(L,0); // at 1 we should have an HybridBase instance... HybridBase *obj = SLB::get(L,1); if (obj == 0) luaL_error(L, "Output(1) of constructor should be an HybridBase instance"); // now our table... to find methods obj->_subclassMethods = this; } return lua_gettop(L); } private: ref_ptr _CI; }; /*--- Internal Hybrid Subclasses ----------------------------------------------*/ HybridBase::HybridBase() : _L(0), _global_environment(0) { SLB_DEBUG_CALL; } HybridBase::~HybridBase() { SLB_DEBUG_CALL; unAttach(); } void HybridBase::attach(lua_State *L) { SLB_DEBUG_CALL; //TODO allow reattaching... if (_L != 0 && _L != L) throw std::runtime_error("Trying to reattach an Hybrid instance"); if (L) { SLB_DEBUG_CLEAN_STACK(L,0); _L = L; lua_newtable(_L); // [+1] //TODO this can be improved a little bit... by storing this metatable //somewhere.... lua_newtable(_L); // [+1] metatable lua_pushvalue(_L, LUA_GLOBALSINDEX); // [+1] globals _G lua_setfield(_L, -2, "__index"); // [-1] metatable.__index = _G lua_setmetatable(L,-2); // [-1] // done _global_environment = luaL_ref(_L, LUA_REGISTRYINDEX); // [-1] } } void HybridBase::unAttach() { SLB_DEBUG_CALL; clearMethodMap(); _subclassMethods = 0; if (_L && _global_environment ) { luaL_unref(_L, LUA_REGISTRYINDEX, _global_environment); _global_environment = 0; _L = 0; } } void HybridBase::clearMethodMap() { SLB_DEBUG_CALL; // delete the list of _methods for(MethodMap::iterator i = _methods.begin(); i != _methods.end(); i++ ) { delete i->second; } _methods.clear(); } bool HybridBase::getMethod(const char *name) const { SLB_DEBUG_CALL; if (_L == 0) throw std::runtime_error("Hybrid instance not attached");\ SLB_DEBUG_STACK(5,_L, "HybridBase(%p)::getMethod '%s' (_L = %p)", this, name, _L); int top = lua_gettop(_L); // first try to find in _subclassMethods if (_subclassMethods.valid()) { //TODO: NEED DEBUG... // ---- why not: // Object *m = obj->_subclassMethods->get(key); // if (m) // { // SLB_DEBUG(5, "Hybrid subclassed instance, looking for '%s' method [OK]", key); // m->push(L); // return 1; // } // else SLB_DEBUG(5, "Hybrid subclassed instance, looking for '%s' method [FAIL!]", key); // ---- instead of: (even though this is quicker) but code above should work lua_pushstring(_L,name); // [+1] _subclassMethods->getCache(_L); // [-1, +1] will pop key's copy and return the cache if (!lua_isnil(_L,-1)) { assert("Invalid Stack" && (lua_gettop(_L) == top+1)); return true; } lua_pop(_L,1); // [-1] remove nil assert("Invalid Stack" && (lua_gettop(_L) == top)); //end TODO------------------------------------------------------------------------------- } ClassInfo *ci = getClassInfo(); ci->push(_L); lua_getmetatable(_L,-1); lua_getfield(_L,-1, "__hybrid"); if (!lua_isnil(_L,-1)) { lua_pushstring(_L,name); lua_rawget(_L,-2); if (!lua_isnil(_L,-1)) { lua_replace(_L,top+1); lua_settop(_L,top+1); SLB_DEBUG(6, "HybridBase(%p-%s)::getMethod '%s' (_L = %p) -> FOUND", this, ci->getName().c_str(),name, _L); assert("Invalid Stack" && (lua_gettop(_L) == top+1)); return true; } else SLB_DEBUG(6, "HybridBase(%p-%s)::getMethod '%s' (_L = %p) -> *NOT* FOUND", this,ci->getName().c_str(), name, _L); } else SLB_DEBUG(4, "HybridBase(%p-%s) do not have any hybrid methods", this, ci->getName().c_str()); // anyway... if not found: lua_settop(_L,top); return false; } void HybridBase::setMethod(lua_State *L, ClassInfo *ci) { SLB_DEBUG_CALL; SLB_DEBUG_CLEAN_STACK(L,-2); // key // value [top] int top = lua_gettop(L); // checks key, value assert( "Invalid key for method" && lua_type(L,top-1) == LUA_TSTRING); assert( "Invalid type of method" && lua_type(L,top) == LUA_TFUNCTION); ci->push(L); // top +1 lua_getmetatable(L,-1); // top +2 lua_getfield(L,-1, "__hybrid"); // top +3 // create if not exists if (lua_isnil(L,-1)) { lua_pop(L,1); // remove nil lua_newtable(L); // top +3 lua_pushstring(L, "__hybrid"); lua_pushvalue(L,-2); // a copy for ClassInfo lua_rawset(L, top+2); // set to he metatable } lua_insert(L,top-2); // put the __hybrid table below key,value lua_settop(L, top+1); // table, key, and value lua_rawset(L,top-2); // set elements lua_settop(L, top-2); // remove everything :) } void HybridBase::registerAsHybrid(ClassInfo *ci) { SLB_DEBUG_CALL; //TODO: Check first if the class already has __index, __newindex //Maybe this should be a feature at ClassInfo to warn in case //the user sets these functions more than once. ci->setClass__newindex( FuncCall::create(class__newindex) ); ci->setClass__index( FuncCall::create(class__index) ); ci->setObject__index( FuncCall::create(object__index) ); ci->setHybrid(); } const HybridBase* get_hybrid(lua_State *L, int pos) { SLB_DEBUG_CALL; //TODO: Generalize this to be used in all SLB const HybridBase *obj = get(L,pos); if (!obj) { if (lua_type(L,pos) == LUA_TUSERDATA) { void *dir = lua_touserdata(L,pos); // try to get the class info: ClassInfo *ci = Manager::getInstance().getClass(L,pos); if (ci == 0) { luaL_error(L, "Invalid Hybrid object (index=%d) " "'%s' %p", pos, ci->getName().c_str(), dir); } else { luaL_error(L, "Invalid Hybrid object (index=%d) " "userdata (NOT REGISTERED WITH SLB) %p", pos, dir); } } else { luaL_error(L, "Invalid Hybrid object (index=%d) found %s", pos, luaL_typename(L,pos)); } } return obj; } int HybridBase::call_lua_method(lua_State *L) { SLB_DEBUG_CALL; const HybridBase *hb = get_hybrid( L, 1 ); if (hb->_L == 0) luaL_error(L, "Instance(%p) not attached to any lua_State...", hb); if (hb->_L != L) luaL_error(L, "This instance(%p) is attached to another lua_State(%p)", hb, hb->_L); // get the real function to call lua_pushvalue(L, lua_upvalueindex(1)); // get the environment (from object) and set it lua_rawgeti(L, LUA_REGISTRYINDEX, hb->_global_environment); lua_setfenv(L,-2); lua_insert(L,1); //put the target function at 1 SLB_DEBUG_STACK(10, L, "Hybrid(%p)::call_lua_method ...", hb); lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); return lua_gettop(L); } int HybridBase::class__newindex(lua_State *L) { SLB_DEBUG_CALL; SLB_DEBUG_CLEAN_STACK(L,-2); // 1 - obj (table with classInfo) ClassInfo *ci = Manager::getInstance().getClass(L,1); if (ci == 0) luaL_error(L, "Invalid Class at #1"); // 2 - key (string) const int key = 2; // 3 - value (func) const int value = 3; if (lua_isstring(L,key) && lua_isfunction(L,value)) { // create a closure with the function to call lua_pushcclosure(L, HybridBase::call_lua_method, 1); // replaces [value] setMethod(L, ci); } else { luaL_error(L, "hybrid instances can only have new methods (functions) " "indexed by strings ( called with: class[ (%s) ] = (%s) )", lua_typename(L, lua_type(L,key)), lua_typename(L, lua_type(L,value)) ); } return 0; } int HybridBase::object__index(lua_State *L) { SLB_DEBUG_CALL; SLB_DEBUG_CLEAN_STACK(L,+1); SLB_DEBUG(4, "HybridBase::object__index"); // 1 - obj (table with classInfo) HybridBase* obj = get(L,1); if (obj == 0) luaL_error(L, "Invalid instance at #1"); if (!obj->_L) luaL_error(L, "Hybrid instance not attached or invalid method"); if (obj->_L != L) luaL_error(L, "Can not use that object outside its lua_state(%p)", obj->_L); // 2 - key (string) (at top) const char *key = lua_tostring(L,2); // call getMethod of hybrid (basic) if(!obj->getMethod(key)) luaL_error(L, "Invalid method %s", key); assert("Invalid stored function" && (lua_type(L,-1) == LUA_TFUNCTION) ); return 1; } int HybridBase::class__index(lua_State *L) { SLB_DEBUG_CALL; SLB_DEBUG_CLEAN_STACK(L,+1); SLB_DEBUG_STACK(6, L, "Call class__index"); // trying to traverse the class... create a new InternalHybridSubclass ClassInfo *ci = Manager::getInstance().getClass(L,1); if (ci == 0) luaL_error(L, "Expected a valid class."); luaL_checkstring(L,2); // only valid with strings if (!ci->hasConstructor()) { luaL_error(L, "Hybrid Class(%s) doesn't have constructor." " You can not subclass(%s) from it", ci->getName().c_str(), lua_tostring(L,2)); } ref_ptr subc = new InternalHybridSubclass(ci); subc->push(L); // -- set cache... lua_pushvalue(L,2); // [+1] key lua_pushvalue(L,-2); // [+1] copy of new InternalHybrid... ci->setCache(L); // [-2] keep a copy in the cache // -- set cache done return 1; } }