view C++/Luae.h @ 202:99d0887395cc

Luae: Add set() function for LuaeTable
author David Demelier <markand@malikania.fr>
date Sat, 04 Jan 2014 18:02:06 +0100
parents 8ee4a34e166c
children b54c83ed71ca
line wrap: on
line source

/*
 * Luae.h -- Lua helpers and such
 *
 * Copyright (c) 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 _LUA_H_
#define _LUA_H_

#include <cassert>
#include <cstddef>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include <lua.hpp>

/* {{{ LuaeState */

/**
 * @class LuaeState
 * @brief Wrapper for lua_State
 *
 * This class automatically create a new Lua state and add implicit
 * cast operator plus RAII destruction.
 */
class LuaeState {
private:
	struct Deleter {
		void operator()(lua_State *L)
		{
			lua_close(L);
		}
	};

	using Ptr = std::unique_ptr<lua_State, Deleter>;

	Ptr m_state;

	void initRegistry();

public:
	/*
	 * FieldRefs:		The field stored into the registry to avoid
	 *			recreation of shared objects.
	 */
	static const char *FieldRefs;

	LuaeState(const LuaeState &) = delete;
	LuaeState &operator=(const LuaeState &) = delete;

	/**
	 * Default constructor. Create a new state.
	 */
	LuaeState();

	/**
	 * Use the already created state.
	 *
	 * @param L the state to use
	 */
	LuaeState(lua_State *L);

	/**
	 * Move constructor.
	 *
	 * @param state the Lua state to move
	 */
	LuaeState(LuaeState &&state);

	/**
	 * Move assignment operator.
	 *
	 * @param state the Lua state to move
	 */
	LuaeState &operator=(LuaeState &&state);

	/**
	 * Implicit cast operator for convenient usage to C Lua API.
	 *
	 * @return the state as lua_State *
	 */
	operator lua_State*();
};

/* }}} */

/* {{{ LuaeValue */

/**
 * @class LuaeValue
 * @brief A fake variant for Lua values
 *
 * This class is primarly used for copying Lua values without checking
 * the types, useful to pass data.
 */
class LuaeValue {
private:
	union {
		lua_Number	 number;
		bool		 boolean;
	};

	int type;
	std::string str;
	std::vector<std::pair<LuaeValue, LuaeValue>> table;

public:
	/**
	 * Dump a value at the specific index.
	 *
	 * @param L the Lua state
	 * @param index the value
	 * @return a tree of values
	 */
	static LuaeValue copy(lua_State *L, int index);

	/**
	 * Push a value to a state.
	 *
	 * @param L the Lua state
	 * @param value the value to push
	 */
	static void push(lua_State *L, const LuaeValue &value);

	/**
	 * Default constructor (type nil)
	 */
	LuaeValue();
};

/* }}} */

/* {{{ LuaeTable */

/**
 * @class LuaeTable
 * @brief Some function for table manipulation
 *
 * Read, reference and get fields from tables.
 */
class LuaeTable {
public:
	using ReadFunction = std::function<void(lua_State *L, int tkey, int tvalue)>;

	/**
	 * 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 get(lua_State *L, int idx, const std::string &name);

	/**
	 * Set a table field. Specialized for the same fields as get.
	 *
	 * @param L the Lua state
	 * @param idx the index
	 * @param name the field name
	 * @param value the value
	 * @see get
	 */
	template <typename T>
	static void set(lua_State *L, int idx, const std::string &name, const T &value);

	/**
	 * Overload for string literals.
	 *
	 * @param L the Lua state
	 * @param idx the index
	 * @param name the field name
	 * @param s the string
	 */
	template <size_t N>
	static void set(lua_State *L, int idx, const std::string &name, const char (&s)[N])
	{
		set<const char *>(L, idx, name, s);
	}

	/**
	 * Require a field from a table.
	 *
	 * @param L the Lua state
	 * @param idx the table index
	 * @param name the field name
	 * @return the value or call luaL_error
	 */
	template <typename T>
	static T require(lua_State *L, int idx, const std::string &name)
	{
		lua_getfield(L, idx, name.c_str());

		if (lua_type(L, -1) == LUA_TNIL)
			luaL_error(L, "missing field `%s'", name.c_str());
			// NOT REACHED

		lua_pop(L, 1);

		return get<T>(L, idx, name);
	}

	/**
	 * Check a table field.
	 *
	 * @param L the Lua state
	 * @param idx the table index
	 * @param name the field name
	 * @return the type
	 */
	static int type(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 read(lua_State *L, int idx, ReadFunction 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 ref(lua_State *L,
		       int idx,
		       int type,
		       const std::string &name);
};

/* }}} */

/* {{{ LuaeClass */

/**
 * @class LuaeClass
 * @brief Support for object oriented programming between C++ and Lua
 *
 * This class provides functions for passing and retrieving objects from C++ and
 * Lua.
 */
class LuaeClass {
public:
	using Methods	= std::vector<luaL_Reg>;

	template <typename T>
	using Ptr	= std::shared_ptr<T>;

	struct Def {
		std::string	name;		//! metatable name
		Methods		methods;	//! methods
		Methods		metamethods;	//! metamethods
		const Def	*parent;	//! optional parent class
	};

	/*
	 * FieldName:		The field stored in the object metatable about 
	 *			the object metatable name.
	 *
	 * FieldParents:	The field that holds all parent classes. It is
	 *			used to verify casts.
	 */
	static const char *FieldName;
	static const char *FieldParents;

	/**
	 * Initialize a new object.
	 *
	 * @param L the Lua state
	 * @param def the definition
	 */
	static void create(lua_State *L, const Def &def);

	/**
	 * Push a shared object to Lua, it also push it to the "__refs"
	 * table with __mode = "v". That is if we need to push the object
	 * again we use the same reference so Lua get always the same
	 * userdata and gain the following benefits:
	 *
	 * 1. The user can use the userdata as table key
	 * 2. A performance gain thanks to less allocations
	 *
	 * @param L the Lua state
	 * @param o the object to push
	 * @param name the object metatable name
	 */
	template <typename T>
	static void push(lua_State *L, Ptr<T> o, const std::string &name)
	{
		lua_getfield(L, LUA_REGISTRYINDEX, LuaeState::FieldRefs);
		assert(lua_type(L, -1) == LUA_TTABLE);

		lua_rawgetp(L, -1, o.get());

		if (lua_type(L, -1) == LUA_TNIL) {
			lua_pop(L, 1);

			new (L, name.c_str()) std::shared_ptr<T>(o);
			
			lua_pushvalue(L, -1);
			lua_rawsetp(L, -3, o.get());
		}

		lua_replace(L, -2);
	}

	/**
	 * Get an object from Lua that was previously push with pushShared.
	 *
	 * @param L the Lua state
	 * @param index the object index
	 * @param meta the object metatable name
	 * @return the object
	 */
	template <typename T>
	static Ptr<T> get(lua_State *L, int index, const char *meta)
	{
		luaL_checktype(L, index, LUA_TUSERDATA);
		if (!luaL_getmetafield(L, index, FieldName))
			luaL_error(L, "invalid type");

		// Get the class name
		const char *name = lua_tostring(L, -1);
		lua_pop(L, 1);

		bool found(false);

		if (std::string(name) == std::string(meta)) {
			found = true;
		} else {
			if (!luaL_getmetafield(L, index, FieldParents))
				luaL_error(L, "invalid type");

			LuaeTable::read(L, -1, [&] (lua_State *L, int, int tvalue) {
				if (tvalue != LUA_TSTRING)
					return;

				const char *tn = lua_tostring(L, -1);
				if (std::string(tn) == std::string(meta))
					found = true;
			});
	
			lua_pop(L, 1);
		}

		if (!found)
			luaL_error(L, "invalid cast from `%s' to `%s'", name, meta);
		
		return *static_cast<Ptr<T> *>(lua_touserdata(L, index));
	}
};

/* }}} */

/* {{{ LuaeEnum */

/**
 * @class LuaeEnum
 * @brief Binds and get enumeration
 *
 * Bind C/C++ enumeration as tables to Lua in the key-value form. Two
 * methods push and get are also available for enumeration flags.
 */
class LuaeEnum {
public:
	/**
	 * The definition of the enumeration
	 */
	using Def = std::unordered_map<std::string, int>;

	/**
	 * Bind the enumeration as a table into an existing table.
	 *
	 * @param L the Lua state
	 * @param def the definition
	 * @param index the table index
	 * @param name the field name to store the enumeration
	 */
	static void create(lua_State *L,
			   const Def &def,
			   int index,
			   const std::string &name);

	/**
	 * Push the value enumeration as a table to Lua. This is used
	 * as the OR replacement.
	 *
	 * @param L the Lua state
	 * @param def the definition
	 * @param value the value
	 */
	static void push(lua_State *L, const Def &def, int value);

	/**
	 * Get the enumeration from Lua. Raises an error if the
	 * value at the index is not a table.
	 *
	 * This is used as the OR replacement.
	 *
	 * @param L the Lua state
	 * @param index the value index
	 * @return the value
	 */
	static int get(lua_State *L, int index);
};

/* }}} */

/* {{{ Luae */

/**
 * @class Luae
 * @brief Add lot of convenience for Lua
 *
 * This class adds lot of functions for Lua and C++.
 */
class Luae {
public:
	/**
	 * 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);


	/**
	 * 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 made object, without testing if its a real
	 * object.
	 *
	 * @param L the Lua state
	 * @param idx the object index
	 * @return the converted object
	 */
	template<class T>
	static T toType(lua_State *L, int idx)
	{
		return reinterpret_cast<T>(lua_touserdata(L, idx));
	}
};

/* }}} */

#if !defined(NDEBUG)

#define LUAE_STACK_CHECKBEGIN(L)						\
	int __topstack = lua_gettop((L))

#define LUAE_STACK_CHECKEQUALS(L)					\
	assert(lua_gettop((L)) == __topstack)

#define LUAE_STACK_CHECKEND(L, cond)					\
	assert(lua_gettop((L)) cond == __topstack)

#else

#define LUAE_STACK_CHECKBEGIN(L)
#define LUAE_STACK_CHECKEQUALS(L)
#define LUAE_STACK_CHECKEND(L, cond)

#endif

void *operator new(size_t size, lua_State *L);

void *operator new(size_t size, lua_State *L, const char *metaname);

void operator delete(void *ptr, lua_State *L);

void operator delete(void *ptr, lua_State *L, const char *metaname);

#endif // !_LUA_H_