changeset 182:15b264d9e833

Add Luae class
author David Demelier <markand@malikania.fr>
date Tue, 29 Oct 2013 21:32:20 +0100
parents 08af4f99c104
children 73146c7c763f
files C++/Luae.cpp C++/Luae.h
diffstat 2 files changed, 649 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Luae.cpp	Tue Oct 29 21:32:20 2013 +0100
@@ -0,0 +1,235 @@
+/*
+ * Luae.cpp -- Lua helpers and such
+ *
+ * Copyright (c) 2011, 2012, 2013 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "Luae.h"
+
+void LuaeClass::createClass(lua_State *L, const LuaObject &cls)
+{
+	LUA_STACK_CHECKBEGIN(L);
+
+	// Already registered?
+	if (luaL_newmetatable(L, cls.name) == 0)
+		return;
+
+	// Add it's "name" field
+	lua_pushstring(L, cls.name);
+	lua_setfield(L, -2, LUAE_CLASS_FIELDNAME);
+
+	// Create a list of parent for a faster cast
+	lua_createtable(L, 0, 0);
+
+	int i = 0;
+	for (auto p = cls.parent; p != nullptr; p = p->parent) {
+		lua_pushstring(L, p->name);
+		lua_rawseti(L, -2, ++i);
+	}
+	lua_setfield(L, -2, LUAE_CLASS_FIELDPARENTS);
+
+	// Metamethods
+	if (cls.metamethods.size() > 0)
+		luaL_setfuncs(L, cls.metamethods.data(), 0);
+
+	// Methods
+	lua_createtable(L, 0, 0);
+
+	if (cls.methods.size() > 0) {
+		luaL_setfuncs(L, cls.methods.data(), 0);
+	}
+
+	/*
+	 * Add a metatable to this __index table so that if
+	 * a method is not found it use the parent table recursively
+	 */
+	if (cls.parent) {
+		// 1. Get the parent 
+		luaL_getmetatable(L, cls.parent->name);
+		assert(lua_type(L, -1) != LUA_TNIL);
+
+		// 2. Create an anonymous metatable
+		lua_createtable(L, 1, 1);
+
+		// 3. Get the __index field from this metatable
+		lua_getfield(L, -2, "__index");
+		assert(lua_type(L, -1) != LUA_TNIL);
+		lua_setfield(L, -2, "__index");
+		lua_setmetatable(L, -3);
+		lua_pop(L, 1);
+	}
+
+	lua_setfield(L, -2, "__index");
+
+	// Pop that metatable
+	lua_pop(L, 1);
+
+	LUA_STACK_CHECKEQUALS(L);
+}
+
+/* --------------------------------------------------------
+ * Luae public methods
+ * -------------------------------------------------------- */
+
+void Luae::createEnum(lua_State *L, const LuaEnum *enumeration)
+{
+	lua_createtable(L, 0, 0);
+
+	for (int i = 0; enumeration[i].name != nullptr; ++i) {
+		lua_pushinteger(L, enumeration[i].value);
+		lua_setfield(L, -2, enumeration[i].name);
+	}
+}
+
+void Luae::createLibrary(lua_State *L, const luaL_Reg *functions)
+{
+	LUA_STACK_CHECKBEGIN(L);
+
+	lua_createtable(L, 0, 0);
+	luaL_setfuncs(L, functions, 0);
+
+	LUA_STACK_CHECKEND(L, - 1);
+}
+
+template <>
+bool Luae::getField(lua_State *L, int idx, const std::string & name)
+{
+	bool value = false;
+
+	lua_getfield(L, idx, name.c_str());
+	if (lua_type(L, idx) == LUA_TBOOLEAN)
+		value = lua_toboolean(L, -1) == 1;
+	lua_pop(L, 1);
+
+	return value;
+}
+
+template <>
+double Luae::getField(lua_State *L, int idx, const std::string & name)
+{
+	double value = 0;
+
+	lua_getfield(L, idx, name.c_str());
+	if (lua_type(L, idx) == LUA_TNUMBER)
+		value = lua_tonumber(L, -1);
+	lua_pop(L, 1);
+
+	return value;
+}
+
+template <>
+int Luae::getField(lua_State *L, int idx, const std::string & name)
+{
+	int value = 0;
+
+	lua_getfield(L, idx, name.c_str());
+	if (lua_type(L, idx) == LUA_TNUMBER)
+		value = lua_tointeger(L, -1);
+	lua_pop(L, 1);
+
+	return value;
+}
+
+template <>
+std::string Luae::getField(lua_State *L, int idx, const std::string & name)
+{
+	std::string value;
+
+	lua_getfield(L, idx, name.c_str());
+	if (lua_type(L, idx) == LUA_TSTRING)
+		value = lua_tostring(L, -1);
+	lua_pop(L, 1);
+
+	return value;
+}
+
+void Luae::preload(lua_State *L, const std::string & name, lua_CFunction func)
+{
+	LUA_STACK_CHECKBEGIN(L);
+
+	lua_getglobal(L, "package");
+	lua_getfield(L, -1, "preload");
+	lua_pushcfunction(L, func);
+	lua_setfield(L, -2, name.c_str());
+	lua_pop(L, 2);
+
+	LUA_STACK_CHECKEQUALS(L);
+}
+
+void Luae::readTable(lua_State *L, int idx, ReadFunction func)
+{
+	lua_pushnil(L);
+
+	if (idx < 0)
+		--idx;
+
+	while (lua_next(L, idx)) {
+		func(L, lua_type(L, -2), lua_type(L, -1));
+		lua_pop(L, 1);
+	}
+
+	lua_pop(L, 1);
+}
+
+int Luae::referenceField(lua_State *L, int idx, int type, const std::string & name)
+{
+	int ref = LUA_REFNIL;
+
+	lua_getfield(L, idx, name.c_str());
+
+	if (lua_type(L, -1) == type) {
+		lua_pushvalue(L, -1);
+		ref = luaL_ref(L, LUA_REGISTRYINDEX);
+	}
+
+	lua_pop(L, 1);
+
+	return ref;
+}
+
+void Luae::require(lua_State *L, const std::string & name, lua_CFunction func, bool global)
+{
+	LUA_STACK_CHECKBEGIN(L);
+
+	luaL_requiref(L, name.c_str(), func, global);
+	lua_pop(L, 1);
+
+	LUA_STACK_CHECKEQUALS(L);
+}
+
+void * operator new(size_t size, lua_State *L)
+{
+	return lua_newuserdata(L, size);
+}
+
+void * operator new(size_t size, lua_State *L, const char *metaname)
+{
+	void *object;
+
+	object = lua_newuserdata(L, size);
+	luaL_setmetatable(L, metaname);
+
+	return object;
+}
+
+void operator delete(void *, lua_State *)
+{
+}
+
+void operator delete(void *, lua_State *L, const char *)
+{
+	lua_pushnil(L);
+	lua_setmetatable(L, -2);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Luae.h	Tue Oct 29 21:32:20 2013 +0100
@@ -0,0 +1,414 @@
+/*
+ * Luae.h -- Lua helpers and such
+ *
+ * Copyright (c) 2011, 2012, 2013 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _LUAE_H_
+#define _LUAE_H_
+
+/**
+ * @file Luae.h
+ * @brief Lua extended API
+ * @author David Demelier
+ *
+ * Object oriented abstraction, common functions and such.
+ */
+
+#include <cassert>
+#include <cstring>
+#include <exception>
+#include <functional>
+#include <memory>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <lua.hpp>
+
+#define LUAE_CLASS_FIELDNAME		"__name"
+#define LUAE_CLASS_FIELDPARENTS		"__parents"
+
+#if !defined(NDEBUG)
+
+#define LUA_STACK_CHECKBEGIN(L)						\
+	int __topstack = lua_gettop((L))
+
+#define LUA_STACK_CHECKEQUALS(L)					\
+	assert(lua_gettop((L)) == __topstack)
+
+#define LUA_STACK_CHECKEND(L, cond)					\
+	assert(lua_gettop((L)) cond == __topstack)
+
+#else
+
+#define LUA_STACK_CHECKBEGIN(L)
+#define LUA_STACK_CHECKEQUALS(L)
+#define LUA_STACK_CHECKEND(L, cond)
+
+#endif
+
+/**
+ * @enum LuaEnum
+ * @brief A class to bind C enums
+ *
+ * Used as a static array, binds C enum with the function Luae::createEnum().
+ */
+struct LuaEnum {
+	const char	*name;
+	int		value;
+};
+
+/**
+ * @struct LuaDeleter
+ * @brief Deletor for lua_State
+ *
+ * Just delete the Lua state at the end of ownership.
+ */
+struct LuaDeleter {
+	void operator()(lua_State *L) {
+		lua_close(L);
+	}
+};
+
+/**
+ * @struct LuaObject
+ * @brief Lua class binding definition
+ *
+ * Define a Lua class (single inheritance only).
+ */
+struct LuaObject {
+	const LuaObject	*parent;
+	const char	*name;
+	std::vector<luaL_Reg> methods;
+	std::vector<luaL_Reg> metamethods;
+};
+
+/**
+ * A lua_State owner with its own deleter.
+ */
+typedef std::unique_ptr<lua_State, LuaDeleter> LuaState;
+
+/**
+ * @class Luae
+ * @brief Functions to initialize object oriented Lua classes
+ *
+ * Provide multiple inheritance and such.
+ */
+class LuaeClass {
+public:
+	/**
+	 * @param L the Lua state
+	 * @param name the class name
+	 */
+	static void createClass(lua_State *L, const LuaObject &cls);
+
+	/**
+	 * Push a object to Lua, the object must have been declared with
+	 * createClass before.
+	 *
+	 * @param L the Lua state
+	 * @param cls the class description
+	 * @param object the object to push
+	 * @see createClass
+	 */
+	template <class T>
+	static void pushObject(lua_State *L,
+			       const LuaObject &cls,
+			       std::shared_ptr<T> object)
+	{
+		new (L, cls.name) std::shared_ptr<T>(object);
+	}
+
+	/**
+	 * Get an object from Lua. The object must have been declared with
+	 * createClass before.
+	 *
+	 * @param L the Lua state
+	 * @param cls the class description
+	 * @param index the object index
+	 * @return the value or throw a luaL_error
+	 */
+	template <class T>
+	static std::shared_ptr<T> getObject(lua_State *L, const LuaObject &cls, int index)
+	{
+		LUA_STACK_CHECKBEGIN(L);
+
+		const char *name;
+		bool found = false;
+
+		/*
+		 * Get the metafield __name to check if it's a correct object.
+		 */
+		luaL_checktype(L, index, LUA_TUSERDATA);
+		if (!luaL_getmetafield(L, index, LUAE_CLASS_FIELDNAME))
+			luaL_error(L, "invalid object");
+			// NOT REACHED
+
+		name = lua_tostring(L, -1);
+		lua_pop(L, 1);
+
+		/*
+		 * Check if that cast is allowed.
+		 */
+		found = strcmp(name, cls.name) == 0;
+		if (!luaL_getmetafield(L, index, LUAE_CLASS_FIELDPARENTS))
+			luaL_error(L, "invalid object");
+			// NOT REACHED
+
+		lua_pushnil(L);
+		while (lua_next(L, -2) && !found) {
+			auto t = lua_tostring(L, -1);
+			found = strcmp(cls.name, t) == 0;
+			lua_pop(L, 1);
+		}
+		lua_pop(L, 1);
+
+		if (!found)
+			luaL_error(L, "invalid cast from %s to %s", name, cls.name);
+			// NOT REACHED
+
+		return *static_cast<const std::shared_ptr<T> *>(lua_topointer(L, index));
+	}
+
+	/**
+	 * Delete an object from Lua. The object must have been declared with
+	 * createClass before.
+	 *
+	 * This function is an helper for your __gc metamethods.
+	 *
+	 * @param L the Lua state
+	 * @param cls the class description
+	 * @param index the object index
+	 * @return 0
+	 */
+	template <class T>
+	static int deleteObject(lua_State *L, const LuaObject &cls, int index)
+	{
+		std::shared_ptr<T> *ptr = static_cast<std::shared_ptr<T> *>(luaL_checkudata(L, index, cls.name));
+
+		ptr->~shared_ptr<T>();
+
+		return 0;
+	}
+};
+
+/**
+ * @class Luae
+ * @brief Lua extended API
+ *
+ * Provide some useful functions and multiple inheritance abstraction.
+ */
+class Luae {
+public:
+	typedef std::function<void(lua_State *L, int tkey, int tvalue)> ReadFunction;
+
+public:
+	/**
+	 * Bind an enumeration as a table into Lua. This function pushes a new table
+	 * mapped by the keys to their value.
+	 *
+	 * @param L the Lua state
+	 * @param enumeration the values to bind
+	 */
+	static void createEnum(lua_State *L, const LuaEnum *enumeration);
+
+	/**
+	 * Create a library table and pushes it onto the stack.
+	 *
+	 * @param L the Lua state
+	 * @param functions the functions
+	 */
+	static void createLibrary(lua_State *L,
+				  const luaL_Reg *functions);
+
+	/**
+	 * Get a field of a specific type from a table. Specialized for the
+	 * types: int, double, bool and string.
+	 *
+	 * @param L the Lua state
+	 * @param idx the table index
+	 * @param name the field name
+	 * @return the converted type.
+	 */
+	template <typename T>
+	static T getField(lua_State *L, int idx, const std::string & name);
+
+	/**
+	 * Read a table, the function func is called for each element in the
+	 * table. Parameter tkey is the Lua type of the key, parameter tvalue is
+	 * the Lua type of the value. The key is available at index -2 and the
+	 * value at index -1.
+	 *
+	 * <strong>Do not pop anything within the function.</strong>
+	 *
+	 * @param L the Lua state
+	 * @param idx the table index
+	 * @param func the function to call
+	 */
+	static void readTable(lua_State *L, int idx, ReadFunction func);
+
+	/**
+	 * Preload a library, it will be added to package.preload so the
+	 * user can successfully call require "name". In order to work, you need
+	 * to open luaopen_package and luaopen_base first.
+	 *
+	 * @param L the Lua state
+	 * @param name the module name
+	 * @param func the opening library
+	 * @see require
+	 */
+	static void preload(lua_State *L,
+			    const std::string & name,
+			    lua_CFunction func);
+
+	/**
+	 * Reference a field from a table at the index. The reference is created in
+	 * the registry only if type matches.
+	 *
+	 * @param L the Lua state
+	 * @param idx the table index
+	 * @param type the type requested
+	 * @param name the field name
+	 * @return the reference or LUA_REFNIL on problem
+	 */
+	static int referenceField(lua_State *L,
+				  int idx,
+				  int type,
+				  const std::string & name);
+
+	/**
+	 * Load a library just like it was loaded with require.
+	 *
+	 * @param L the Lua state
+	 * @param name the module name
+	 * @param func the function
+	 * @param global store as global
+	 */
+	static void require(lua_State *L,
+			    const std::string & name,
+			    lua_CFunction func,
+			    bool global);
+
+	/**
+	 * Convert a new-placement created type to the requested template.
+	 *
+	 * @param L the Lua state
+	 * @param idx the object index
+	 * @param metaname which metatable
+	 * @return the type
+	 */
+	template <typename T>
+	static T toType(lua_State *L, int idx, const char *metaname)
+	{
+		return reinterpret_cast<T>(luaL_checkudata(L, idx, metaname));
+	}
+
+	/**
+	 * Get a table field from a specific allowed range.
+	 *
+	 * @param L the Lua state
+	 * @param idx the value index
+	 * @param name the field name
+	 * @param min the minimum
+	 * @param max the maximum
+	 * @return the converted value
+	 * @throw NotValidScript on failure
+	 */
+	template <typename T>
+	static T getRanged(lua_State *L,
+			   int idx,
+			   const char *name,
+			   int min,
+			   int max,
+			   int def)
+	{
+		LUA_STACK_CHECKBEGIN(L);
+
+		T ret;
+
+		lua_getfield(L, idx, name);
+		if (lua_type(L, -1) == LUA_TNUMBER) {
+			int i = lua_tonumber(L, -1);
+	
+			ret = (i < min || i > max) ? def : static_cast<T>(i);
+		} else {
+			ret = def;
+		}
+
+		lua_pop(L, 1);
+
+		LUA_STACK_CHECKEQUALS(L);
+
+		return ret;
+	}
+
+	template <typename T>
+	static T requireRanged(lua_State *L,
+			       int idx,
+			       const char *name,
+			       int min,
+			       int max)
+	{
+		LUA_STACK_CHECKBEGIN(L);
+
+		lua_getfield(L, idx, name);
+		if (lua_type(L, -1) != LUA_TNUMBER) {
+			std::ostringstream oss;
+			lua_pop(L, 1);
+
+			oss << "field `" << name;
+			oss << "': is not a number";
+
+			throw std::invalid_argument(oss.str());
+		}
+
+		int i = lua_tonumber(L, -1);
+
+		lua_pop(L, 1);
+		if (i < min || i > max) {
+			std::ostringstream oss;
+
+			oss << "field `" << name;
+			oss << "': is out of range [" << min;
+			oss << ", " << max << "] expected ";
+			oss << " got: " << i;
+
+			throw std::invalid_argument(oss.str());
+		}
+
+		LUA_STACK_CHECKEQUALS(L);
+
+		return static_cast<T>(i);
+	}
+};
+
+void * operator new(size_t size, lua_State *L);
+
+void * operator new(size_t size, lua_State *L, const char *metaname);
+
+/*
+ * Delete operators are just present to avoid some warnings, Lua does garbage
+ * collection so these functions just do nothing.
+ */
+
+void operator delete(void *ptr, lua_State *L);
+
+void operator delete(void *ptr, lua_State *L, const char *metaname);
+
+#endif // !_LUA_H_