Skip to content

Commit

Permalink
struct changed to use userdata blocks. Structs are handled by-ref in …
Browse files Browse the repository at this point in the history
…Lua code to match handling of regular tables.

struct and class have uniform support for: methods, properties, member variable accessors.
Refactor common code into helpers.d
Added support for const function args.
pushMethod enhanced to support struct's aswell.
New alias syntax updates.
  • Loading branch information
TurkeyMan committed Sep 2, 2014
1 parent ff8dfe5 commit f10c1d5
Show file tree
Hide file tree
Showing 5 changed files with 689 additions and 184 deletions.
239 changes: 164 additions & 75 deletions luad/conversions/classes.d
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ See the source code for details.
*/
module luad.conversions.classes;

import luad.conversions.helpers;
import luad.conversions.functions;

import luad.c.all;
Expand All @@ -14,94 +15,175 @@ import luad.base;
import core.memory;

import std.traits;
import std.string : toStringz;
import std.typetuple;
import std.typecons;

extern(C) private int classCleaner(lua_State* L)

void pushGetter(T, string member)(lua_State* L)
{
GC.removeRoot(lua_touserdata(L, 1));
return 0;
alias RT = typeof(mixin("T."~member));
final class C
{
static if((!isMemberFunction!(T, member) || returnsRef!(AliasMember!(T, member))) && isUserStruct!RT)
{
ref RT get()
{
T _this = *cast(T*)&this;
return mixin("_this."~member);
}
}
else
{
RT get()
{
T _this = *cast(T*)&this;
return mixin("_this."~member);
}
}
}

lua_pushlightuserdata(L, (&C.init.get).funcptr);
lua_pushcclosure(L, &methodWrapper!(typeof(&C.init.get), T, false), 1);
}

private void pushMeta(T)(lua_State* L, T obj)
private void pushGetters(T)(lua_State* L)
{
if(luaL_newmetatable(L, T.mangleof.ptr) == 0)
return;
lua_newtable(L); // -2 is getters
lua_newtable(L); // -1 is methods

pushValue(L, T.stringof);
lua_setfield(L, -2, "__dclass");
// populate getters
foreach(member; __traits(derivedMembers, T))
{
static if(!skipMember!(T, member) &&
!isStaticMember!(T, member) &&
member != "Monitor")
{
static if(isMemberFunction!(T, member) && !isProperty!(T, member))
{
static if(canCall!(T, member))
{
pushMethod!(T, member)(L);
lua_setfield(L, -2, member.ptr);
}
}
else static if(canRead!(T, member)) // TODO: move into the getter for inaccessable fields (...and throw a useful error messasge)
{
pushGetter!(T, member)(L);
lua_setfield(L, -3, member.ptr);
}
}
}

pushValue(L, T.mangleof);
lua_setfield(L, -2, "__dmangle");
lua_pushcclosure(L, &index, 2);
}

lua_newtable(L); //__index fallback table
void pushSetter(T, string member)(lua_State* L)
{
// TODO: This is broken if setter argument is different from the getter's return type...
alias ArgType = typeof(mixin("T."~member));

foreach(member; __traits(derivedMembers, T))
final class C
{
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(isUserStruct!ArgType)
{
static if(__traits(getOverloads, T.init, member).length > 0 && !__traits(isStaticFunction, mixin("T." ~ member)))
final void set(ref ArgType value)
{
pushMethod!(T, member)(L);
lua_setfield(L, -2, toStringz(member));
T _this = *cast(T*)&this;
mixin("_this."~member) = value;
}
}
else
{
final void set(ArgType value)
{
T _this = *cast(T*)&this;
mixin("_this."~member) = value;
}
}
}

lua_setfield(L, -2, "__index");
lua_pushlightuserdata(L, (&C.init.set).funcptr);
lua_pushcclosure(L, &methodWrapper!(typeof(&C.init.set), T, false), 1);
}

private void pushSetters(T)(lua_State* L)
{
lua_newtable(L);

// populate setters
foreach(member; __traits(derivedMembers, T))
{
static if(!skipMember!(T, member) &&
!isStaticMember!(T, member) &&
canWrite!(T, member) && // TODO: move into the setter for readonly fields (...and throw a useful error messasge)
member != "Monitor")
{
static if(!isMemberFunction!(T, member) || isProperty!(T, member))
{
pushSetter!(T, member)(L);
lua_setfield(L, -2, member.ptr);
}
}
}

pushMethod!(T, "toString")(L);
lua_setfield(L, -2, "__tostring");
lua_pushcclosure(L, &newIndex, 1);
}

pushMethod!(T, "opEquals")(L);
lua_setfield(L, -2, "__eq");
private void pushMeta(T)(lua_State* L)
{
if(luaL_newmetatable(L, T.mangleof.ptr) == 0)
return;

//TODO: handle opCmp here
pushValue(L, T.stringof);
lua_setfield(L, -2, "__dtype");

// TODO: mangled names can get REALLY long in D, it might be nicer to store a hash instead?
pushValue(L, T.mangleof);
lua_setfield(L, -2, "__dmangle");

lua_pushcfunction(L, &classCleaner);
lua_pushcfunction(L, &userdataCleaner);
lua_setfield(L, -2, "__gc");

pushGetters!T(L);
lua_setfield(L, -2, "__index");
pushSetters!T(L);
lua_setfield(L, -2, "__newindex");

// TODO: look into why we can't call these on const objects... that's insane, right?
static if(canCall!(T, "toString"))
{
pushMethod!(T, "toString")(L);
lua_setfield(L, -2, "__tostring");
}
static if(canCall!(T, "opEquals"))
{
pushMethod!(T, "opEquals")(L);
lua_setfield(L, -2, "__eq");
}

// TODO: handle opCmp here

// TODO: operators, etc...

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);
Rebindable!T* ud = cast(Rebindable!T*)lua_newuserdata(L, obj.sizeof);
*ud = obj;

pushMeta(L, obj);
lua_setmetatable(L, -2);

GC.addRoot(ud);

pushMeta!T(L);
lua_setmetatable(L, -2);
}

//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
//TODO: handle foreign userdata properly (i.e. raise errors)
verifyType!T(L, idx);

Object obj = *cast(Object*)lua_touserdata(L, idx);
return cast(T)obj;
Expand All @@ -112,36 +194,42 @@ 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)
// For use as __call
void pushCallMetaConstructor(T)(lua_State* L)
{
static if(__traits(compiles, mixin("&T." ~ member)))
static if(!hasCtor!T)
{
static if(is(typeof(mixin("&T.init." ~ member)) == delegate))
enum isStaticMember = __traits(isStaticFunction, mixin("T." ~ member));
else
enum isStaticMember = true;
static T ctor(LuaObject self)
{
static if(is(T == class))
return new T;
else
return T.init;
}
}
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);
// TODO: handle each constructor overload in a loop.
// TODO: handle each combination of default args
alias Ctors = typeof(__traits(getOverloads, T.init, "__ctor"));
alias Args = ParameterTypeTuple!(Ctors[0]);

static T ctor(LuaObject self, Args args)
{
static if(is(T == class))
return new T(args);
else
return T(args);
}
}

pushFunction(L, &ctor);
lua_setfield(L, -2, "__call");
}

// 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)
void pushStaticTypeInterface(T)(lua_State* L) if(is(T == class) || is(T == struct))
{
lua_newtable(L);

Expand All @@ -152,11 +240,7 @@ void pushStaticTypeInterface(T)(lua_State* L)
return;
}

static if(hasCtor!T)
{
pushCallMetaConstructor!T(L);
lua_setfield(L, -2, "__call");
}
pushCallMetaConstructor!T(L);

lua_newtable(L);

Expand All @@ -165,7 +249,12 @@ void pushStaticTypeInterface(T)(lua_State* L)
static if(isStaticMember!(T, member))
{
enum isFunction = is(typeof(mixin("T." ~ member)) == function);
static if(isFunction)
enum isProperty = (functionAttributes!(mixin("T." ~ member)) & FunctionAttribute.property);
else
enum isProperty = false;

// TODO: support static properties
static if(isFunction)
pushValue(L, mixin("&T." ~ member));
else
Expand Down
Loading

0 comments on commit f10c1d5

Please sign in to comment.