-
Notifications
You must be signed in to change notification settings - Fork 36
LuaD enhancements #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
25b2bcd
409f91e
3a56818
ee7ab6d
7ededc8
f395d01
488994e
f4acf02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -14,134 +15,172 @@ 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) | ||
| { | ||
| GC.removeRoot(lua_touserdata(L, 1)); | ||
| return 0; | ||
| } | ||
|
|
||
| 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 | ||
| private void pushSetters(T)(lua_State* L) | ||
| { | ||
| lua_newtable(L); | ||
|
|
||
| // populate setters | ||
| 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(!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(__traits(getOverloads, T.init, member).length > 0 && !__traits(isStaticFunction, mixin("T." ~ member))) | ||
| static if(!isMemberFunction!(T, member) || isProperty!(T, member)) | ||
| { | ||
| pushMethod!(T, member)(L); | ||
| lua_setfield(L, -2, toStringz(member)); | ||
| pushSetter!(T, member)(L); | ||
| lua_setfield(L, -2, member.ptr); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| lua_setfield(L, -2, "__index"); | ||
|
|
||
| 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... | ||
|
|
||
| // set the parent metatable | ||
| static if(BaseClassesTuple!T.length > 0) | ||
| { | ||
| pushMeta!(BaseClassesTuple!T[0])(L); | ||
| lua_setmetatable(L, -2); | ||
| } | ||
|
|
||
| // create a '__metatable' entry to protect the metatable against modification | ||
| 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 | ||
| //TODO: handle foreign userdata properly (i.e. raise errors) | ||
| verifyType!T(L, idx); | ||
|
|
||
| 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; | ||
| return *cast(T*)lua_touserdata(L, idx); | ||
| } | ||
|
|
||
| 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); | ||
|
|
||
|
|
@@ -152,11 +191,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); | ||
|
|
||
|
|
@@ -165,7 +200,12 @@ void pushStaticTypeInterface(T)(lua_State* L) | |
| static if(isStaticMember!(T, member)) | ||
| { | ||
| enum isFunction = is(typeof(mixin("T." ~ member)) == function); | ||
| static if(isFunction) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Static if not needed; it's all handled in the then-clause
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, it is, I just didn't remove it from the 'then' expression. |
||
| 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 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ Is this Particular pushMeta block in any of your feature-specific branches, @TurkeyMan ? My modification to get interfaces to work touches this and I haven't been able to find it elsewhere.