Mercurial > code
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_