Skip to content

Instantly share code, notes, and snippets.

@alex-ac
Last active June 16, 2023 15:46
Show Gist options
  • Save alex-ac/4514013 to your computer and use it in GitHub Desktop.
Save alex-ac/4514013 to your computer and use it in GitHub Desktop.
Lua C++ wrapper. C++11 templates street magic.
#include <Lua.hh>
extern "C" {
#include <lualib.h>
#include <lauxlib.h>
};
using namespace util;
int Lua::call(lua_State *vm) {
auto function = (std::function<int(Lua&)> *)
lua_touserdata(vm, lua_upvalueindex(1));
auto l = Lua(vm);
return (*function)(l);
}
Lua::Lua(lua_State *vm):
del(false),
vm(vm)
{}
Lua::Lua():
del(true),
vm(luaL_newstate())
{
luaL_openlibs(vm);
}
Lua::~Lua() {
for (auto lambda : lambdas)
delete lambda;
if (del)
lua_close(vm);
}
void Lua::lambda(std::function<int(Lua&)> *function, const std::string& name) {
lambdas.push_back(function);
userdata(function);
closure(Lua::call);
save(name);
}
void Lua::file(const std::string& name) {
luaL_dofile(vm, name.c_str()) && lua_error(vm);
}
void Lua::load(const std::string& name, int i) {
int t = i;
if (lua_gettop(vm) - (i>0?i:-i) < 0 || !lua_istable(vm, i)) {
if (-1 == i)
t = LUA_GLOBALSINDEX;
else
luaL_error(vm, "Invalid load operation (out of stack)!");
}
lua_getfield(vm, t, name.c_str());
}
void Lua::pop(const int i) {
if (lua_gettop(vm) - i < 0)
luaL_error(vm, "Invalid pop operation (out of stack)!");
lua_pop(vm, i);
}
void Lua::remove(const int i) {
if (lua_gettop(vm) - (i>0?i:-i) < 0)
luaL_error(vm, "Invalid remove operation (out of stack)!");
lua_remove(vm, i);
}
void Lua::table() {
lua_newtable(vm);
}
void Lua::metatable(const int i) {
if (lua_gettop(vm) - (i>0?i:-i) < 0 || !lua_istable(vm, i))
luaL_error(vm, "Invalid set metatable operation (out of stack)!");
lua_setmetatable(vm, i);
}
void Lua::closure(int (*callback)(lua_State *), const int i) {
if (lua_gettop(vm) - i < 0)
luaL_error(vm, "Invalid closure operation (out of stack)!");
lua_pushcclosure(vm, callback, i);
}
void Lua::copy(const int i) {
if (lua_gettop(vm) - (i>0?i:-i) < 0)
luaL_error(vm, "Invalid copy operation (out of stack)!");
lua_pushvalue(vm, i);
}
void Lua::save(const std::string& name, const int i) {
int t = i;
if (lua_gettop(vm) - (i>0?i:-i) < 0 || !lua_istable(vm, i)) {
if (-2 == i)
t = LUA_GLOBALSINDEX;
else
luaL_error(vm, "Invalid save operation (out of stack)!");
}
lua_setfield(vm, t, name.c_str());
}
bool Lua::is_nil(const int i) {
if (lua_gettop(vm) - (i>0?i:-i) < 0)
luaL_error(vm, "Invalid is nil operation (out of stack)!");
return lua_isnil(vm, i);
}
void Lua::object(const LuaClass *object, const std::string& name) {
load(name);
table();
load("mtab", -2);
metatable();
remove(-2);
userdata(object);
save("__self__");
}
LuaClass * Lua::object(const int i) {
if (lua_gettop(vm) - (i>0?i:-i) < 0)
luaL_error(vm, "Invalid object operation (out of stack)!");
if (!lua_istable(vm, i))
luaL_error(vm, "Invalid object (table expected)!");
load("__self__", i);
auto ret = (LuaClass *)userdata();
pop();
return ret;
}
void Lua::userdata(const void *d) {
lua_pushlightuserdata(vm, const_cast<void *>(d));
}
void * Lua::userdata(const int i) {
if (lua_gettop(vm) - (i>0?i:-i) < 0)
luaL_error(vm, "Invalid userdata operation (out of stack)! %d, %d", lua_gettop(vm), i);
if (!lua_isuserdata(vm, i))
luaL_error(vm, "Invalid userdata operation (userdata expected)!");
return lua_touserdata(vm, i);
}
void Lua::number(const lua_Number n) {
lua_pushnumber(vm, n);
}
lua_Number Lua::tonumber(const int i) {
if (lua_gettop(vm) - (i>0?i:-i) < 0)
luaL_error(vm, "Invalid tonumber operation (out of stack)!");
if (!lua_isnumber(vm, i))
luaL_error(vm, "Invalid tonumber operation (number expected)!");
return lua_tonumber(vm, i);
}
void Lua::string(const std::string& string) {
lua_pushstring(vm, string.c_str());
}
std::string Lua::string(const int i) {
if (lua_gettop(vm) - (i>0?i:-i) < 0)
luaL_error(vm, "Invalid string operation (out of stack)!");
if (!lua_isstring(vm, i))
luaL_error(vm, "Invalid string operation (string expected)!");
return lua_tostring(vm, i);
}
void Lua::boolean(const bool b) {
lua_pushboolean(vm, b);
}
bool Lua::boolean(const int i) {
if (lua_gettop(vm) - (i>0?i:-i) < 0)
luaL_error(vm, "Invalid boolean operation (out of stack)!");
if (!lua_isboolean(vm, i))
luaL_error(vm, "Invalid boolean operation (boolean expected)!");
return lua_toboolean(vm, i);
}
void Lua::LuaObject::export_class(Lua& vm) {
}
void Lua::LuaObject::export_me(Lua& vm) {
vm.export_class<LuaObject>();
}
const std::string Lua::LuaObject::class_name() {
return "Object";
}
const std::string Lua::LuaObject::obj_class_name() const {
return "Object";
}
void Lua::export_function(const std::string& name, void (*callback)()) {
auto function = new std::function<int(Lua&)>([callback] (Lua& vm) -> int {
(*callback)();
return 0;
});
lambda(function, name);
}
template <>
int Lua::ret<lua_Number>(const lua_Number r) {
number(r);
return 1;
}
template <>
int Lua::ret<std::string>(const std::string r) {
string(r);
return 1;
}
template <>
int Lua::ret<bool>(const bool r) {
boolean(r);
return 1;
}
template <>
int Lua::ret<int>(const int r) {
number(r);
return 1;
}
template <>
std::string Lua::arg<std::string>(const int i) {
return string(i);
}
template <>
lua_Number Lua::arg<lua_Number>(const int i) {
return tonumber(i);
}
template <>
bool Lua::arg<bool>(const int i) {
return boolean(i);
}
template <>
lua_Integer Lua::arg<lua_Integer>(const int i) {
return tonumber(i);
}
template <>
int Lua::arg<int>(const int i) {
return tonumber(i);
}
#pragma once
#include <string>
#include <vector>
#include <functional>
#include <tuple>
#include <type_traits>
#include <iostream>
extern "C" {
#include <lua.h>
};
namespace util {
class LuaClass {
private:
protected:
public:
virtual const std::string obj_class_name() const = 0;
};
class Lua {
protected:
template <typename T>
int ret(const T r) {
static_assert(std::is_convertible<T, LuaClass*>::value,
"LuaClass * required!");
object((LuaClass *)r, std::remove_pointer<T>::type::class_name());
return 1;
}
template <class T>
T arg(const int i) {
if (std::is_base_of<util::LuaClass, T>::value) {
return *(T *)object(i);
} else {
return *(T *)userdata(i);
}
}
template <class T>
T* argp(const int i) {
if (std::is_base_of<util::LuaClass, T>::value) {
return (T *)object(i);
} else {
return (T *)userdata(i);
}
}
template <typename T, typename T1, typename... Args>
std::tuple<T, T1, Args...> args(const int i = 1) {
T t = arg<T>( i);
return std::tuple_cat(t, args<T1, Args...>( i + 1));
}
template <typename T>
std::tuple<T> args(const int i = 1) {
return std::tuple<T>(arg<T>( i));
}
template <typename... Args> struct sizer {
static const int size = sizeof...(Args);
};
template <int N> struct apply_method {
template <class T, typename R, typename... MethodArgs,
typename... TupleArgs, typename... Args>
static R apply(T* o, R (T::*method)(MethodArgs...),
std::tuple<TupleArgs...>& t, Args... args) {
return
apply_method<N-1>::apply(o, method, t, std::get<N-1>(t), args...);
}
};
template <int N> struct apply_function {
template <typename R, typename... FunctionArgs, typename... TupleArgs,
typename... Args>
static R apply(R (*function)(FunctionArgs...),
std::tuple<TupleArgs...>& t, Args... args) {
return
apply_function<N-1>::apply(function, t, std::get<N-1>(t), args...);
}
};
private:
bool del;
lua_State * vm;
std::vector<std::function<int(Lua&)> *> lambdas;
static int call(lua_State *vm);
public:
Lua(lua_State *vm);
Lua();
~Lua();
void lambda(std::function<int(Lua&)> *function, const std::string& name);
void load(const std::string& name, const int i = -1);
void pop(const int i = 1);
void remove(const int i);
void table();
void metatable(const int i = -2);
void closure(int (*)(lua_State *), const int i = 1);
void copy(const int i = -1);
void save(const std::string& name, const int i = -2);
void file(const std::string& name);
bool is_nil(const int i = -1);
void object(const LuaClass *, const std::string& name);
LuaClass * object(const int i = -1);
void userdata(const void *);
void * userdata(const int i = -1);
void number(const lua_Number);
lua_Number tonumber(const int i = -1);
void string(const std::string&);
std::string string(const int i = -1);
void boolean(const bool);
bool boolean(const int i = -1);
class LuaObject : public LuaClass {
private:
protected:
public:
static void export_class(Lua& vm);
static void export_me(Lua& vm);
static const std::string class_name();
virtual const std::string obj_class_name() const override;
};
/*
* class LuaObject: public LuaClass {};
* class P : public LuaClass {};
* class T : public LuaClass {};
*
* -> T::export_me(;
* -> export_class<T, P>();
* -> T::class_name();
* -> P::class_name();
* -> P::export_me(;
* -> export_class<P>();
* -> P::class_name();
* -> LuaObject::class_name();
* -> LuaObject::export_me(;
* -> export_class<LuaObject>();
* -> LuaObject::class_name();
* -> LuaObject::class_name();
* -> LuaObject::export_class(;
* -> P::export_class(;
* -> T::export_class(;
*/
template<class T, class P = LuaObject>
void export_class() {
static_assert(std::is_base_of<util::LuaClass, T>::value,
"LuaClass implementation expected!");
auto name = T::class_name();
auto parent_name = P::class_name();
bool parent = name != parent_name;
if (parent) {
P::export_me(*this);
}
load(name);
if (!is_nil()) {
pop();
return;
}
pop();
table();
table();
copy(-2);
save("__index");
save("mtab");
if (parent) {
load(parent_name);
load("mtab");
metatable(-3);
pop();
}
T::export_class(*this);
save(name);
}
template <typename R, class T, typename... Args>
void export_method(const std::string& name,
R (T::*method)(Args...)) {
auto function = new std::function<int(Lua&)>([method] (Lua& vm) -> int {
auto tuple = vm.args<Args...>( 2);
return vm.ret(
apply_method<std::tuple_size<decltype(tuple)>::value>
::apply(vm.argp<T>( 1), method, tuple));
});
lambda(function, name);
}
template <class T, typename... Args>
void export_method(const std::string& name,
void (T::*method)(Args...)) {
auto function = new std::function<int(Lua&)>([method] (Lua& vm) -> int {
auto tuple = vm.args<Args...>( 2);
apply_method<std::tuple_size<decltype(tuple)>::value>
::apply(vm.argp<T>( 1), method, tuple);
return 0;
});
lambda(function, name);
}
template <typename R, class T>
void export_method(const std::string& name, R (T::*method)()) {
auto function = new std::function<int(Lua&)>([method] (Lua& vm) -> int {
return vm.ret( (vm.argp<T>( 1)->*method)());
});
lambda(function, name);
}
template <class T>
void export_method(const std::string& name, void (T::*method)()) {
auto function = new std::function<int(Lua&)>([method] (Lua& vm) -> int {
(vm.argp<T>( 1)->*method)();
return 0;
});
lambda(function, name);
}
template <typename R, typename... Args>
void export_function(const std::string& name,
R (*callback)(Args...)) {
auto function = new std::function<int(Lua&)>([callback] (Lua& vm) -> int {
auto tuple = vm.args<Args...>();
return vm.ret(
apply_function<std::tuple_size<decltype(tuple)>::value>
::apply(callback, tuple));
});
lambda(function, name);
}
template <typename... Args>
void export_function(const std::string& name,
void (*callback)(Args...)) {
auto function = new std::function<int(Lua&)>([callback] (Lua& vm) -> int {
auto tuple = vm.args<Args...>();
apply_function<std::tuple_size<decltype(tuple)>::value>
::apply(callback, tuple);
return 0;
});
lambda(function, name);
}
template <typename R>
void export_function(const std::string& name, R (*callback)()) {
auto function = new std::function<int(Lua&)>([callback] (Lua& vm) -> int {
return vm.ret( (*callback)());
});
lambda(function, name);
}
void export_function(const std::string& name, void (*callback)());
};
template <>
int Lua::ret<lua_Number>(const lua_Number r);
template <>
int Lua::ret<std::string>(const std::string r);
template <>
int Lua::ret<bool>(const bool r);
template <>
int Lua::ret<int>(const int r);
template <>
std::string Lua::arg<std::string>(const int i);
template <>
lua_Number Lua::arg<lua_Number>(const int i);
template <>
lua_Integer Lua::arg<lua_Integer>(const int i);
template <>
int Lua::arg<int>(const int i);
template <>
bool Lua::arg<bool>(const int i);
template <> struct Lua::apply_method<0> {
template <class T, typename R, typename... MethodArgs,
typename... TupleArgs, typename... Args>
static R apply(T* o, R (T::*method)(MethodArgs...),
std::tuple<TupleArgs...>& t, Args... args) {
return (o->*method)(args...);
}
};
template <> struct Lua::apply_function<0> {
template <typename R, typename... FunctionArgs, typename... TupleArgs,
typename... Args>
static R apply(R (*function)(FunctionArgs...),
std::tuple<TupleArgs...>& t, Args... args) {
return
(*function)(args...);
}
};
} // namespace util;
#include <iostream>
#include <Lua.hh>
void test() {
std::cout << "Hello, world! " << std::endl;
}
void test1(int a) {
std::cout << a << std::endl;
}
int test2() {
return 2;
}
int test3(int a) {
return a + 1;
}
class test_class : public util::LuaClass {
public:
static test_class *construct() {
return new test_class();
}
void deconstruct() {
delete this;
}
static void export_me(util::Lua& vm) {
vm.export_class<test_class>();
}
static void export_class(util::Lua& vm) {
vm.export_function("new", &test_class::construct);
vm.export_method("delete", &test_class::deconstruct);
vm.export_method("test", &test_class::test);
vm.export_method("test1", &test_class::test1);
vm.export_method("test2", &test_class::test2);
vm.export_method("test3", &test_class::test3);
}
static const std::string class_name() {
return "test_class";
}
virtual const std::string obj_class_name() const override {
return "test_class";
}
void test() {
std::cout << "Hello, world [test_class]" << std::endl;
}
int test1() {
return 1;
}
int test2(int a) {
return a*2;
}
void test3(int a) {
std::cout << a << std::endl;
}
};
int main(int argc, char *argv[]) {
util::Lua l;
l.export_function("test", &test);
l.export_function("test1", &test1);
l.export_function("test2", &test2);
l.export_function("test3", &test3);
test_class::export_me(l);
l.file("test.lua");
return 0;
}
test()
test1(1)
print(test2())
print(test3(3))
t = test_class.new()
print(t)
t:test()
print(t:test1())
print(t:test2(2))
t:test3(3)
t:delete()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment