Proof of concept Re: STL table using metatable to access

Thomas Nelson <[email protected]>
Newsgroups gmane.comp.lang.lua.bind.user
Message-ID <[email protected]>
This code is still WAY preliminary but I wanted to throw it out there as 
a proof of concept and see if anybody had any thoughts on it.  I've not 
even tested it beyond checking to see if I can access the items in the 
table.

It consists of two basic parts, a converter for translating a pointer to 
an STL collection into a lua table and an adapter that uses meta table 
methods to call the underlying STL collection access methods. There is 
also an attempt to use a weak table to preserve identity but I'm not 
convinced that works yet.

Outstanding issues:
- This has only been specialized for std::vector at this point.  I've 
kept the metamethods split out from the adapter so they can be 
specialized individually.
- Lua 5.1 does not respect the __length metamethod for tables (this 
appears to have been fixed for 5.2)  Additionally I'm not sure what the 
right behavior should be since #table is defined to be the index of the 
last non nil entry and if I have a vector of pointers some of which are 
NULL, returning the length of the vector would violate the rule.
- [i]pairs and [i]next.  Lua does not directly support meta methods for 
these and inext calls rawgeti and so does not call the __index 
metamethod.  I've found a patch/extension here 
(http://lua-users.org/wiki/GeneralizedPairsAndIpairs) that fixes this 
for inext, but I don't know if that's a good final solution.
- I'm storing some book-keeping info in the table itself... I've not 
decided yet if that's a good idea or not.
- The identity code isn't quite right yet.  I should be able to do a = 
source; b = source; a == b and get back true.
- It sure as heck isn't thread safe.

Things I'd like to add someday...
- a const pointer will create a read only table
- triggers on add/set (rather than putting values directly into the 
tables.)

TomN

On 3/25/2010 1:25 PM, Thomas Nelson wrote:
> Before I dive into this too far, has anyone thought of/implemented using
> a metatable directly access a C++ STL container as a lua table?
>
> Correct me if I'm wrong but container_policy.hpp just copies the values
> of the existing table into a lua table and returns that.  Couldn't we
> apply a meta table with "__index" "__newindex" and "__len" methods
> specialized to the container type.
>
> TomN
>

------------------------------------------------------------------------------
Download Intel&#174; Parallel Studio Eval
Try the new software tools for yourself. Speed compiling, find bugs
proactively, and fine-tune applications for parallel performance.
See why Intel Parallel Studio got high marks during beta.
http://p.sf.net/sfu/intel-sw-dev

_______________________________________________
luabind-user mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/luabind-user
stl_container_converter.hpp (text/plain, 1.1 KB)
#ifndef LUABIND_STL_CONTAINER_CONVERTER_100325_HPP
# define LUABIND_STL_CONTAINER_CONVERTER_100325_HPP

# include <luabind/get_main_thread.hpp>
# include <luabind/handle.hpp>
# include <luabind/detail/policy.hpp>
# include <luabind/detail/stl_container_adapter.hpp>

namespace luabind {


//#define LUABIND_STL_CONVERTER( type )		
template <class T>							
struct default_converter< std::vector< T > *>		
{											
    typedef boost::mpl::false_		is_native;
	typedef std::vector< typename T > *	container;

    template <class U>							
    int match(lua_State* L, U, int index)
    {
		return detail::stl_adapter<std::vector< T > >::container_match(L, index);
    }

    template <class U>
    std::vector<T> *apply(lua_State* L, U, int index)
    {
		return detail::stl_adapter<std::vector< T > >::container_from(L, index);
    }

	void apply(lua_State* L, std::vector<T> * p)
    {
		detail::stl_adapter<std::vector< T > >::container_to(L, p);
    }

    template <class U>
    void converter_postcall(lua_State*, U const&, int)
    {}
};

} // namespace luabind

#endif // LUABIND_SHARED_PTR_CONVERTER_090211_HPP
stl_container_adapter.hpp (text/plain, 6 KB)
#ifndef LUABIND_STL_CONTAINER_ADAPTER_HPP_INCLUDED
#define LUABIND_STL_CONTAINER_ADAPTER_HPP_INCLUDED

#include <luabind/config.hpp>
#include <luabind/detail/policy.hpp>

namespace luabind
{
namespace detail
{
	static const char * container_lud_tag = "__luabind_stl_lud";
	static const char * container_ref_tag = "__luabind_stl_ref";

	//----------------------------------------
	template <class Container_tt>
	int stl_container__len(lua_State *L);

	template <class Container_tt>
	int stl_container__index(lua_State *L);

	template <class Container_tt>
	int stl_container__newindex(lua_State *L);

	//----------------------------------------
	void stl_register_collection(lua_State *L, void *p);
	bool stl_find_table_for_collection(lua_State *L, void *p);

	//----------------------------------------
	template <class Container_tt>
	struct stl_adapter 
	{
		typedef typename Container_tt container_t;

		static int container_create_metatable(lua_State *L)
		{
			lua_newtable(L);

			lua_pushstring(L, "__len");
			lua_pushcclosure(L, &stl_container__len<container_t>, 0);
			lua_rawset(L, -3);
			lua_pushstring(L, "__index");
			lua_pushcclosure(L, &stl_container__index<container_t>, 0);
			lua_rawset(L, -3);
			lua_pushstring(L, "__newindex");
			lua_pushcclosure(L, &stl_container__newindex<container_t>, 0);
			lua_rawset(L, -3);

			ref_stl_adapter = luaL_ref(L, LUA_REGISTRYINDEX);

			lua_rawgeti(L, LUA_REGISTRYINDEX, ref_stl_adapter);

			return ref_stl_adapter;
		}

		static void container_push_lua_table(lua_State *L, container_t *pContainer)
		{
			int nOldTable;

			lua_newtable(L);
			/* *TSN*: Should I clone the metatable rather than setting this state data here? */
			lua_pushstring(L, container_lud_tag);
			lua_pushlightuserdata(L, pContainer);
			lua_rawset(L, -3);

			nOldTable = lua_getmetatable(L, -1);

			if (ref_stl_adapter == LUA_REFNIL)
				container_create_metatable(L);
			else
				lua_rawgeti(L, LUA_REGISTRYINDEX, ref_stl_adapter);
			
			if (nOldTable)
			{
				lua_insert(L, -2); // swap the tables
				lua_setmetatable(L, -2);
			}

			lua_setmetatable(L, -2);

			/* *TSN*: Should I clone the metatable rather than setting this state data here? */
			lua_pushstring(L, container_ref_tag);
			lua_pushinteger(L, ref_stl_adapter);
			lua_rawset(L, -3);
		}

		static container_t * container_from_table(lua_State *L, int index)
		{
			container_t *pcontainer;

			if (!lua_istable(L, index))
				return 0;

			lua_pushstring(L, container_lud_tag);
			lua_rawget(L, index);
			pcontainer = static_cast<container_t *>(lua_touserdata(L, -1));
			lua_pop(L, 1);
			return pcontainer;
		}

		static int container_match(lua_State *L, int index)
		{
			if (!lua_istable(L, index))
				return -1;	// not a table at all

			// must be a table AND must be a compatible STL table
			lua_pushstring(L, detail::container_ref_tag);
			lua_rawget(L, index);
			if (lua_isnil(L, -1))
			{	// not one of our tables at all
				lua_pop(L, 1);
				return -1;
			}
			int nRefId = lua_tointeger(L, -1);
			lua_pop(L, 1);

			return (nRefId == ref_stl_adapter) ? 0 : -1;
		}

		static container_t *container_from(lua_State* L, int index)
		{
			lua_pushstring(L, detail::container_lud_tag);
			lua_rawget(L, index);

			container_t *raw_ptr = static_cast<container_t *>(lua_touserdata(L, -1));
			lua_pop(L, 1);
			return raw_ptr;
		}

		static void container_to(lua_State* L, container_t *p)
		{
			if (!stl_find_table_for_collection(L, p))
			{
				container_push_lua_table(L, p);
				stl_register_collection(L, p);
			}
			// table now at top of stack
		}

		static int ref_stl_adapter;

	};

	template <class Container_tt>
	int stl_adapter<Container_tt>::ref_stl_adapter = LUA_REFNIL;

	/**
	 * Table length operation. '#table'
	 * metamethod
	 * --- 
	 * @Note technically this is incorrect it returns the number of items in
	 * the STL collection, however Lua (2.2.5) defines the length of a table
	 * as follows:
	 * "The length of a table t is defined to be any integer index n such 
	 * that t[n] is not nil and t[n+1] is nil."
	 *
	 * But do I want to scan looking for a nil value?
	 */
	template <class Container_tt>
	int stl_container__len(lua_State *L)
	{	/* stack:
		 * 1: table object
		 */
		Container_tt *container = stl_adapter<Container_tt>::container_from_table(L, 1);
		
		int iSize(0);
		if (container->empty())
			 iSize = container->size() + 1;

		lua_pushnumber(L, iSize);
		return 1;
	}

	/**
	 * metatable method, accessors. val = table[key];
	 * Note that lua indexes are 1 based.
	 */
	template <class Container_tt>
	int stl_container__index(lua_State *L)
	{	/*
		 * 1: table
		 * 2: key
		 */
		default_converter<Container_tt::value_type> converter;
		Container_tt *	container = stl_adapter<Container_tt>::container_from_table(L, 1);
		int				index = lua_tointeger(L, 2);

		if ((index < 1) || (index >= (int)container->size()))
		{	// the key is not convertible to an integer or is outside our range
			// use the raw table
			lua_pushvalue(L, 2);
			lua_rawget(L, 1);
			return 1;
		}

		converter.apply(L, container->at(index - 1));
						
		return 1;
	}

	/**
	 * metatable method, setter. table[key] = value;
	 * Note that lua indexes are 1 based.
	 */
	template <class Container_tt>
	int stl_container__newindex(lua_State *L)
	{	/*
		 * 1: table object
		 * 2: key
		 * 3: value
		 */
		default_converter<typename Container_tt::value_type> converter;
		Container_tt *	container = stl_adapter<Container_tt>::container_from_table(L, 1);
		int				index = lua_tointeger(L, 2);

		if (index < 1)
		{	// the key is not convertible to an integer 
			// use the raw table
			lua_pushvalue(L, 2);
			lua_pushvalue(L, 3);
			lua_rawset(L, 1);
			return 0;
		}
		if (index >= (int)container->size())
		{
			container->resize(index);
		}

		container->at(index) = converter.apply(L, 
			LUABIND_DECORATE_TYPE(Container_tt::value_type), 3);

		return 0;
	}


}
}

#endif
stl_container_adapter.cpp (text/plain, 1.2 KB)
#define LUABIND_BUILDING

#include <luabind/config.hpp>
#include <luabind/lua_include.hpp>
#include <luabind/detail/stl_container_adapter.hpp>

namespace luabind
{	namespace detail
{
static int s_stl_weak_registry = LUA_REFNIL;

static void stl_weak_registry_create(lua_State *L)
{
	lua_newtable(L);
	lua_newtable(L);
	lua_pushstring(L, "__mode");
	lua_pushstring(L, "v");
	lua_settable(L, -3);
	lua_setmetatable(L, -2);

	s_stl_weak_registry = luaL_ref(L, LUA_REGISTRYINDEX);
}

// table should be at top of stack
void stl_register_collection(lua_State *L, void *p)
{

	if (s_stl_weak_registry == LUA_REFNIL)
	{
		stl_weak_registry_create(L);
	}
	lua_pushinteger(L, s_stl_weak_registry);
	lua_gettable(L, LUA_REGISTRYINDEX);
	// registry now at top of stack
	lua_pushlightuserdata(L, p);
	lua_pushvalue(L, -3);	// copy the table
	lua_settable(L, -3);
	lua_pop(L, 1);
}

bool stl_find_table_for_collection(lua_State *L, void *p)
{
	lua_pushinteger(L, s_stl_weak_registry);
	lua_gettable(L, LUA_REGISTRYINDEX);

	if (lua_isnil(L, -1))
	{	// not found
		lua_pop(L, 1);
		return false;
	}
	lua_pushlightuserdata(L, p);
	lua_gettable(L, -2);

	if (lua_isnil(L, -1))
	{
		lua_pop(L, 1);
		return false;
	}
	return true;
}


}
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.