289 lines
5.6 KiB
D
289 lines
5.6 KiB
D
/**
|
|
Internal module for pushing and getting class types.
|
|
This feature is still a work in progress, currently, only the simplest of _classes are supported.
|
|
See the source code for details.
|
|
*/
|
|
module luad.conversions.classes;
|
|
|
|
import luad.conversions.functions;
|
|
|
|
import luad.c.all;
|
|
import luad.stack;
|
|
import luad.base;
|
|
|
|
import core.memory;
|
|
|
|
import std.traits;
|
|
import std.string : toStringz;
|
|
|
|
extern(C) private int classCleaner(lua_State* L)
|
|
{
|
|
GC.removeRoot(lua_touserdata(L, 1));
|
|
return 0;
|
|
}
|
|
|
|
private void pushMeta(T)(lua_State* L, T obj)
|
|
{
|
|
if(luaL_newmetatable(L, T.mangleof.ptr) == 0)
|
|
return;
|
|
|
|
pushValue(L, T.stringof);
|
|
lua_setfield(L, -2, "__dclass");
|
|
|
|
pushValue(L, T.mangleof);
|
|
lua_setfield(L, -2, "__dmangle");
|
|
|
|
lua_newtable(L); //__index fallback table
|
|
|
|
foreach(member; __traits(derivedMembers, T))
|
|
{
|
|
static if(__traits(getProtection, __traits(getMember, T, member)) == "public" && //ignore non-public fields
|
|
member != "this" && member != "__ctor" && //do not handle
|
|
member != "Monitor" && member != "toHash" && //do not handle
|
|
member != "toString" && member != "opEquals" && //handle below
|
|
member != "opCmp") //handle below
|
|
{
|
|
static if(__traits(getOverloads, T.init, member).length > 0 && !__traits(isStaticFunction, mixin("T." ~ member)))
|
|
{
|
|
pushMethod!(T, member)(L);
|
|
lua_setfield(L, -2, toStringz(member));
|
|
}
|
|
}
|
|
}
|
|
|
|
lua_setfield(L, -2, "__index");
|
|
|
|
pushMethod!(T, "toString")(L);
|
|
lua_setfield(L, -2, "__tostring");
|
|
|
|
pushMethod!(T, "opEquals")(L);
|
|
lua_setfield(L, -2, "__eq");
|
|
|
|
//TODO: handle opCmp here
|
|
|
|
|
|
lua_pushcfunction(L, &classCleaner);
|
|
lua_setfield(L, -2, "__gc");
|
|
|
|
lua_pushvalue(L, -1);
|
|
lua_setfield(L, -2, "__metatable");
|
|
}
|
|
|
|
void pushClassInstance(T)(lua_State* L, T obj) if (is(T == class))
|
|
{
|
|
T* ud = cast(T*)lua_newuserdata(L, obj.sizeof);
|
|
*ud = obj;
|
|
|
|
pushMeta(L, obj);
|
|
lua_setmetatable(L, -2);
|
|
|
|
GC.addRoot(ud);
|
|
}
|
|
|
|
//TODO: handle foreign userdata properly (i.e. raise errors)
|
|
T getClassInstance(T)(lua_State* L, int idx) if (is(T == class))
|
|
{
|
|
if(lua_getmetatable(L, idx) == 0)
|
|
{
|
|
luaL_error(L, "attempt to get 'userdata: %p' as a D object", lua_topointer(L, idx));
|
|
}
|
|
|
|
lua_getfield(L, -1, "__dmangle"); //must be a D object
|
|
|
|
static if(!is(T == Object)) //must be the right object
|
|
{
|
|
size_t manglelen;
|
|
auto cmangle = lua_tolstring(L, -1, &manglelen);
|
|
if(cmangle[0 .. manglelen] != T.mangleof)
|
|
{
|
|
lua_getfield(L, -2, "__dclass");
|
|
auto cname = lua_tostring(L, -1);
|
|
luaL_error(L, `attempt to get instance %s as type "%s"`, cname, toStringz(T.stringof));
|
|
}
|
|
}
|
|
lua_pop(L, 2); //metatable and metatable.__dmangle
|
|
|
|
Object obj = *cast(Object*)lua_touserdata(L, idx);
|
|
return cast(T)obj;
|
|
}
|
|
|
|
template hasCtor(T)
|
|
{
|
|
enum hasCtor = __traits(compiles, __traits(getOverloads, T.init, "__ctor"));
|
|
}
|
|
|
|
// TODO: exclude private members (I smell DMD bugs...)
|
|
template isStaticMember(T, string member)
|
|
{
|
|
static if(__traits(compiles, mixin("&T." ~ member)))
|
|
{
|
|
static if(is(typeof(mixin("&T.init." ~ member)) == delegate))
|
|
enum isStaticMember = __traits(isStaticFunction, mixin("T." ~ member));
|
|
else
|
|
enum isStaticMember = true;
|
|
}
|
|
else
|
|
enum isStaticMember = false;
|
|
}
|
|
|
|
// For use as __call
|
|
void pushCallMetaConstructor(T)(lua_State* L)
|
|
{
|
|
alias typeof(__traits(getOverloads, T.init, "__ctor")) Ctor;
|
|
|
|
static T ctor(LuaObject self, ParameterTypeTuple!Ctor args)
|
|
{
|
|
return new T(args);
|
|
}
|
|
|
|
pushFunction(L, &ctor);
|
|
}
|
|
|
|
// TODO: Private static fields are mysteriously pushed without error...
|
|
// TODO: __index should be a function querying the static fields directly
|
|
void pushStaticTypeInterface(T)(lua_State* L)
|
|
{
|
|
lua_newtable(L);
|
|
|
|
enum metaName = T.mangleof ~ "_static";
|
|
if(luaL_newmetatable(L, metaName.ptr) == 0)
|
|
{
|
|
lua_setmetatable(L, -2);
|
|
return;
|
|
}
|
|
|
|
static if(hasCtor!T)
|
|
{
|
|
pushCallMetaConstructor!T(L);
|
|
lua_setfield(L, -2, "__call");
|
|
}
|
|
|
|
lua_newtable(L);
|
|
|
|
foreach(member; __traits(derivedMembers, T))
|
|
{
|
|
static if(isStaticMember!(T, member))
|
|
{
|
|
enum isFunction = is(typeof(mixin("T." ~ member)) == function);
|
|
|
|
static if(isFunction)
|
|
pushValue(L, mixin("&T." ~ member));
|
|
else
|
|
pushValue(L, mixin("T." ~ member));
|
|
|
|
lua_setfield(L, -2, member.ptr);
|
|
}
|
|
}
|
|
|
|
lua_setfield(L, -2, "__index");
|
|
|
|
lua_setmetatable(L, -2);
|
|
}
|
|
|
|
version(unittest)
|
|
{
|
|
import luad.testing;
|
|
private lua_State* L;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
L = luaL_newstate();
|
|
|
|
static class A
|
|
{
|
|
private:
|
|
string s;
|
|
|
|
public:
|
|
int n;
|
|
|
|
this(int n, string s)
|
|
{
|
|
this.n = n;
|
|
this.s = s;
|
|
}
|
|
|
|
string foo(){ return s; }
|
|
|
|
int bar(int i)
|
|
{
|
|
return n += i;
|
|
}
|
|
|
|
void verifyN(int n)
|
|
{
|
|
assert(this.n == n);
|
|
}
|
|
}
|
|
|
|
static class B : A
|
|
{
|
|
this(int a, string s)
|
|
{
|
|
super(a, s);
|
|
}
|
|
|
|
override string foo() { return "B"; }
|
|
|
|
override string toString() { return "B"; }
|
|
}
|
|
|
|
void addA(in char* name, A a)
|
|
{
|
|
pushValue(L, a);
|
|
lua_setglobal(L, name);
|
|
}
|
|
|
|
auto a = new A(2, "foo");
|
|
addA("a", a);
|
|
|
|
pushValue(L, a.toString());
|
|
lua_setglobal(L, "a_toString");
|
|
|
|
auto b = new B(2, "foo");
|
|
addA("b", b);
|
|
addA("otherb", b);
|
|
|
|
pushValue(L, (A a)
|
|
{
|
|
assert(a);
|
|
a.bar(2);
|
|
});
|
|
lua_setglobal(L, "func");
|
|
|
|
luaL_openlibs(L);
|
|
unittest_lua(L, `
|
|
--assert(a.n == 2)
|
|
assert(a:bar(2) == 4)
|
|
--assert(a.n == 4)
|
|
func(a)
|
|
assert(a:bar(2) == 8)
|
|
|
|
--a.n = 42
|
|
--a:verifyN(42)
|
|
--assert(a.n == 42)
|
|
|
|
assert(a:foo() == "foo")
|
|
assert(tostring(a) == a_toString)
|
|
|
|
assert(b:bar(2) == 4)
|
|
func(b)
|
|
assert(b:bar(2) == 8)
|
|
|
|
assert(b:foo() == "B")
|
|
assert(tostring(b) == "B")
|
|
|
|
assert(a ~= b)
|
|
assert(b == otherb)
|
|
`);
|
|
|
|
pushValue(L, cast(B)null);
|
|
lua_setglobal(L, "c");
|
|
unittest_lua(L, `assert(c == nil)`);
|
|
|
|
pushValue(L, (B b) => assert(b is null));
|
|
lua_setglobal(L, "checkNull");
|
|
unittest_lua(L, `checkNull(nil)`);
|
|
}
|