lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi there,

I would like some comments on my multiple inheritance implementation
from C++. But first, I would like to thanks levi on #lua@freenode.net
who helped me much on this :-).

First, let's explain how I did.

Let say I have a base object, here it's "Object". I create a metatable
in the registry named "Object" and then add a table of it's method
into its __index field.

So when I try to access any field, it will lookup over the __index
table of method, this is much easy and the most common usage.

Now, I create a subclass Widget with some methods, I create a
metatable again, plus a table of its method to __index just like
before. This class will derive from Object.

The only thing change now is, I create a new metatable and add
"parents" field set to table which contains "Object", then I add a
__index field that points to a special lookup function. This function
will iterate over all "parents" and call getfield on it. Finally I set
this new table as the metatable of the Widget's method tables (aka
__index).

To summary :

Object is a metatable store in the registry, it has a __index field
set to a table of functions.

Widget is also a metatable stored in the registry, also has an __index
field set to table of functions, however this __index table has a
metatable with "parents" set to { "Object" } and has a __index
function that lookup over parents.

I've attached my C++ implementation, I didn't have much ideas about
classes so sorry about the non-sense of them but we have :

Object
 ^
 |
Widget
 ^
 |
Button      Foo
       ^          ^
       |           |
        Multiple

And with the attached file, if you push a userdata with "Multiple" you
are able to call any method from any classes that inherits from.

I would like some comments on my implementation as it is a very
important part of my future project, so I want to be sure it's good,
secure and performant.

Can you please tell me :

1. Any security issue?
2. Performance issue?
3. Too complex implementation?

Any comments are welcome :-)

The essential functions to check are createClass, createMetatable,
createInheritance and the lookup function.

Regards,

--
Demelier David
#include <iostream>
#include <string>
#include <vector>

#include <lua.hpp>

using namespace std;

/* -------------------------------------------------------
 * LuaClass helpers
 * ------------------------------------------------------- */

struct LuaClass {
	std::string			name;
	std::vector<std::string>	inherits;

	const luaL_Reg			*methods;
	const luaL_Reg			*metamethods;

	LuaClass()
		: methods(nullptr)
		, metamethods(nullptr)
	{
	}
};

static int lookup(lua_State *L)
{
	string key;

	luaL_checktype(L, 1, LUA_TTABLE);
	key = luaL_checkstring(L, 2);

	// Do not have "inherits"?
	luaL_getmetafield(L, 1, "inherits");
	if (lua_type(L, -1) == LUA_TNIL)
		return 1;

	// Iterate over them
	lua_pushnil(L);
	while (lua_next(L, -2)) {
		luaL_getmetatable(L, lua_tostring(L, -1));
		lua_getfield(L, -1, "__index");
		lua_getfield(L, -1, key.c_str());

		// Found?
		if (lua_type(L, -1) != LUA_TNIL) {
			lua_insert(L, 3);
			lua_settop(L, 3);

			return 1;
		}

		lua_pop(L, 4);
	}

	lua_pop(L, 1);
	lua_pushnil(L);

	return 1;
}

/**
 * This function will update the metatable's __index field of the object to
 * add a new metatable with a __index function to lookup parents.
 *
 * Example:
 *
 * +-----------------+        +-----------------+
 * | Metatable A     |        | Methods         |
 * +-----------------+        +-----------------+
 * | __index      -> |        | methodA()       |
 * +-----------------+        | methodB()       |
 *                            +-----------------+
 *
 * After:
 * +-----------------+        +-----------------+
 * | Metatable A     |        | Methods         |
 * +-----------------+        +-----------------+
 * | __index      -> |        | __index  F (1.) | -> lookup function
 * +-----------------+        | inherits T (2.) | -> { "B" }
 *                            +-----------------+
 *                            | methodA()       |
 *                            | methodB()       |
 *                            +-----------------+
 *
 * The table of methods of A's __index is updated with a new metatable that
 * has a __index field set to a lookup function (figure 1). This lookup function
 * will iterate over the inherits table of classes (figure 2) and check if that
 * class has a __index[key] where key is the one wanted.
 *
 * So a typical usage:
 *
 * 	object:method()
 *
 * 1. The __index table of the object metatable check if the table methods has
 *    the `method' key.
 * 2. If the key is not present, it use the __index of that table of methods and
 *    iterate over the parents, so here, B's __index table of methods.
 * 3. And if not found, go to 2.
 */
static void createInheritance(lua_State *L, const LuaClass & lc)
{
	luaL_getmetatable(L, lc.name.c_str());
	lua_getfield(L, -1, "__index");

	lua_createtable(L, 1, 1);
	lua_pushcfunction(L, lookup);
	lua_setfield(L, -2, "__index");
	lua_createtable(L, lc.inherits.size(), lc.inherits.size());

	int i = 1;
	for (const string & s : lc.inherits) {
		lua_pushstring(L, s.c_str());
		lua_rawseti(L, -2, i++);
	}

	lua_setfield(L, -2, "inherits");
	lua_setmetatable(L, -2);
	lua_pop(L, 2);
}

/**
 * This function will create a metatable for the object in the registry. Then
 * we add a table of methods into it's __index field.
 */
static bool createMetatable(lua_State *L, const LuaClass & lcs)
{
	if (lcs.methods == nullptr)
		return false;

	luaL_newmetatable(L, lcs.name.c_str());

	// Add optional metamethods, except __index
	if (lcs.metamethods != nullptr)
		luaL_setfuncs(L,  lcs.metamethods, 0);

	// Add methods
	lua_createtable(L, 0, 0);
	luaL_setfuncs(L, lcs.methods, 0);
	lua_setfield(L, -2, "__index");
	lua_pop(L, 1);

	return true;
}

void createClass(lua_State *L, const LuaClass & lcs)
{
	if (createMetatable(L, lcs) && lcs.inherits.size() > 0)
		createInheritance(L, lcs);
}

/* --------------------------------------------------------
 * Object class
 * -------------------------------------------------------- */

class Object {
public:
	Object() { }
	~Object() { }

	virtual string typeOf() = 0;
};

/**
 * This function will check in the registry for the metatable passed
 * as the first parameter and then the function in its __index[key].
 *
 * If found, it pushes this function and optional all arguments on the
 * stack then call lua_call.
 *
 * Example of use: object:call("SpecificClass, "SpecificMethod", a1, a2, ...)
 */
static int l_call(lua_State *L)
{
	string cls, key;

	luaL_checktype(L, 1, LUA_TUSERDATA);
	cls = luaL_checkstring(L, 2);
	key = luaL_checkstring(L, 3);

	luaL_getmetatable(L, cls.c_str());
	lua_getfield(L, -1, "__index");
	lua_getfield(L, -1, key.c_str());

	// Move the userdata just above cls / key
	lua_pushvalue(L, 1);
	lua_insert(L, 4);

	// Move the function (or nil) from top
	lua_insert(L, 4);
	lua_pop(L, 2);

	// Finally call everything
	lua_call(L, lua_gettop(L) - 4, LUA_MULTRET);

	return lua_gettop(L) - 3;
}

static int l_typeOf(lua_State *L)
{
	Object *o = *(Object **)lua_touserdata(L, 1);

	lua_pushstring(L, o->typeOf().c_str());

	return 1;
}

static const luaL_Reg objectMethods[] = {
	{ "call",		l_call			},
	{ "typeOf",		l_typeOf		},
	{ nullptr,		nullptr			}
};

static int luaopen_object(lua_State *L)
{
	LuaClass lc;

	lc.name		= "Object";
	lc.methods	= objectMethods;

	createClass(L, lc);

	return 0;
}

/* --------------------------------------------------------
 * Widget class
 * -------------------------------------------------------- */

class Widget : public Object {
public:
	Widget() { }
	~Widget() { }

	void draw()
	{
		puts("Drawing a widget");
	}

	virtual string typeOf()
	{
		return "Widget";
	}
};

static int l_widgetDraw(lua_State *L)
{
	Widget *w = *(Widget **)lua_touserdata(L, 1);

	w->draw();

	return 0;
}

static const luaL_Reg widgetMethods[] = {
	{ "draw",		l_widgetDraw	},
	{ nullptr,		nullptr		}
};

static int luaopen_widget(lua_State *L)
{
	LuaClass lc;

	lc.name		= "Widget";
	lc.methods	= widgetMethods;

	// Inherits from "Object"
	lc.inherits.push_back("Object");

	createClass(L, lc);

	return 0;
}

/* --------------------------------------------------------
 * Button class
 * -------------------------------------------------------- */

class Button : public Widget {
private:
	std::string m_text;

public:
	Button()
		: m_text("Button")
	{
	}

	~Button() { }

	std::string getText()
	{
		return m_text;
	}

	void setText(std::string text)
	{
		m_text = text;
	}

	virtual string typeOf()
	{
		return "Button";
	}
};

static int l_buttonGetText(lua_State *L)
{
	Button *b = *(Button **)lua_touserdata(L, 1);

	lua_pushstring(L, b->getText().c_str());

	return 1;
}

static int l_buttonSetText(lua_State *L)
{
	Button *b = *(Button **)lua_touserdata(L, 1);
	string text = luaL_checkstring(L, 2);

	b->setText(text);
	
	return 0;
}

static const luaL_Reg buttonMethods[] = {
	{ "getText",		l_buttonGetText	},
	{ "setText",		l_buttonSetText	},
	{ nullptr,		nullptr		},
};

static int luaopen_button(lua_State *L)
{
	LuaClass lc;

	lc.name		= "Button";
	lc.methods	= buttonMethods;

	// Inherits from "Widget" (implicit "Object")
	lc.inherits.push_back("Widget");

	createClass(L, lc);

	return 0;
}

/* --------------------------------------------------------
 * Foo class
 * -------------------------------------------------------- */

class Foo {
public:
	Foo() { }
	~Foo() { }

	void speak()
	{
		std::cout << "Foo is speaking" << std::endl;
	}

	virtual string typeOf()
	{
		return "Foo";
	}
};

static int l_fooSpeak(lua_State *L)
{
	Foo *foo = *(Foo **)lua_touserdata(L, 1);

	foo->speak();

	return 0;
}

static const luaL_Reg fooMethods[] = {
	{ "speak",		l_fooSpeak	},
	{ nullptr,		nullptr		},
};

static int luaopen_foo(lua_State *L)
{
	LuaClass lc;

	lc.name		= "Foo";
	lc.methods	= fooMethods;

	createClass(L, lc);

	return 0;
}

/* --------------------------------------------------------
 * Multiple class < Foo & Button
 * -------------------------------------------------------- */

class Multiple
	: public Foo
	, public Button
{
public:
	Multiple() { }
	~Multiple() { }

	void kill()
	{
		std::cout << "Multiple is killing" << std::endl;
	}

	virtual std::string typeOf()
	{
		return "Multiple";
	}
};

static int l_multipleKill(lua_State *L)
{
	Multiple *m = *(Multiple **)lua_touserdata(L, 1);

	m->kill();

	return 0;
}

static const luaL_Reg multipleMethods[] = {
	{ "kill",		l_multipleKill	},
	{ nullptr,		nullptr		},
};

static int luaopen_multiple(lua_State *L)
{
	LuaClass lc;

	lc.name		= "Multiple";
	lc.methods	= multipleMethods;

	// Inherits from "Button" and "Foo"
	lc.inherits.push_back("Foo");
	lc.inherits.push_back("Button");

	createClass(L, lc);

	return 0;
}

/* --------------------------------------------------------
 * Just a helper function on the scope of the test
 * -------------------------------------------------------- */

static int createIt(lua_State *L)
{
	Multiple **m = (Multiple **)lua_newuserdata(L, sizeof (void *));
	*m = new Multiple();
	luaL_setmetatable(L, "Multiple");

	return 1;
}

int main(void)
{
	lua_State *L = luaL_newstate();

	luaL_requiref(L, "_G", luaopen_base, 1);
	luaL_requiref(L, "package", luaopen_package, 1);
	luaL_requiref(L, "debug", luaopen_debug, 1);

	luaL_requiref(L, "object", luaopen_object, 1);
	luaL_requiref(L, "widget", luaopen_widget, 1);
	luaL_requiref(L, "button", luaopen_button, 1);
	luaL_requiref(L, "foo", luaopen_foo, 1);
	luaL_requiref(L, "multiple", luaopen_multiple, 1);
	lua_pop(L, 8);

	lua_pushcfunction(L, createIt);
	lua_setglobal(L, "createIt");

	if (luaL_dofile(L, "test.lua") != LUA_OK) {
		cerr << lua_tostring(L, -1) << endl;
	}

	return 0;
}