Initial commit
This commit is contained in:
288
source/luad/conversions/classes.d
Normal file
288
source/luad/conversions/classes.d
Normal file
@ -0,0 +1,288 @@
|
||||
/**
|
||||
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)`);
|
||||
}
|
||||
Reference in New Issue
Block a user