From 67feb7b64017776098cfa09d2b95a35fbe5e678d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 18 Aug 2014 03:12:12 +1000 Subject: [PATCH 1/2] struct changed to use userdata blocks. Structs are handled by-ref in 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. --- luad/conversions/classes.d | 187 +++++++++++-------- luad/conversions/functions.d | 158 ++++++++++------ luad/conversions/helpers.d | 341 +++++++++++++++++++++++++++++++++++ luad/conversions/structs.d | 170 ++++++++++++----- luad/stack.d | 79 ++++++-- 5 files changed, 746 insertions(+), 189 deletions(-) create mode 100644 luad/conversions/helpers.d diff --git a/luad/conversions/classes.d b/luad/conversions/classes.d index cc9d78a..e058e5b 100644 --- a/luad/conversions/classes.d +++ b/luad/conversions/classes.d @@ -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,94 +15,119 @@ 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... + 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; @@ -112,36 +138,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); @@ -152,11 +184,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 +193,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 diff --git a/luad/conversions/functions.d b/luad/conversions/functions.d index fd4b932..ff3abfc 100644 --- a/luad/conversions/functions.d +++ b/luad/conversions/functions.d @@ -14,11 +14,15 @@ Typesafe varargs is supported when pushing _functions to Lua, but as of DMD 2.05 */ module luad.conversions.functions; +import luad.conversions.helpers; +import luad.all; + import core.memory; import std.range; import std.string : toStringz; import std.traits; import std.typetuple; +import std.typecons; import luad.c.all; @@ -35,71 +39,85 @@ private void argsError(lua_State* L, int nargs, ptrdiff_t expected) template StripHeadQual(T : const(T*)) { - alias const(T)* StripHeadQual; + alias StripHeadQual = const(T)*; } template StripHeadQual(T : const(T[])) { - alias const(T)[] StripHeadQual; + alias StripHeadQual = const(T)[]; } template StripHeadQual(T : immutable(T*)) { - alias immutable(T)* StripHeadQual; + alias StripHeadQual = immutable(T)*; } template StripHeadQual(T : immutable(T[])) { - alias immutable(T)[] StripHeadQual; + alias StripHeadQual = immutable(T)[]; } template StripHeadQual(T : T[]) { - alias T[] StripHeadQual; + alias StripHeadQual = T[]; } template StripHeadQual(T : T*) { - alias T* StripHeadQual; + alias StripHeadQual = T*; } template StripHeadQual(T : T[N], size_t N) { - alias T[N] StripHeadQual; + alias StripHeadQual = T[N]; } template StripHeadQual(T) { - alias T StripHeadQual; + alias StripHeadQual = T; } template FillableParameterTypeTuple(T) { - alias staticMap!(StripHeadQual, ParameterTypeTuple!T) FillableParameterTypeTuple; + alias FillableParameterTypeTuple = staticMap!(StripHeadQual, ParameterTypeTuple!T); } template BindableReturnType(T) { - alias StripHeadQual!(ReturnType!T) BindableReturnType; + alias BindableReturnType = StripHeadQual!(ReturnType!T); +} + +template TreatArgs(T...) +{ + static if(T.length == 0) + alias TreatArgs = TypeTuple!(); + else static if(isUserStruct!(T[0])) // TODO: we might do this for static arrays too in future...? + // we need to convert struct's into Ref's because 'ref' isn't part of the type in D, and it gets lots in the function calling logic + alias TreatArgs = TypeTuple!(Ref!(T[0]), TreatArgs!(T[1..$])); + else static if(is(T[0] == class)) + alias TreatArgs = TypeTuple!(Rebindable!(T[0]), TreatArgs!(T[1..$])); + else + alias TreatArgs = TypeTuple!(T[0], TreatArgs!(T[1..$])); } //Call with or without return value, propagating Exceptions as Lua errors. //This should rather be throwing a userdata with __tostring and a reference to //the thrown exception, as it is now, everything but the error type and message is lost. -int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) - if(!is(BindableReturnType!T == const) && - !is(BindableReturnType!T == immutable)) +int callFunction(T, RT = BindableReturnType!T)(lua_State* L, T func, ParameterTypeTuple!T args) + if((returnsRef!T && isUserStruct!RT) || + (!is(RT == const) && !is(RT == immutable))) { - alias BindableReturnType!T RetType; - enum hasReturnValue = !is(RetType == void); - - static if(hasReturnValue) - RetType ret; - try { - static if(hasReturnValue) - ret = func(args); + static if(!is(RT == void)) + { + // TODO: should we support references for all types? + static if(returnsRef!T && isUserStruct!RT) + auto ret = Ref!RT(func(args)); + else + RT ret = func(args); + return pushReturnValues(L, ret); + } else func(args); } @@ -108,18 +126,17 @@ int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) luaL_error(L, "%s", toStringz(e.toString())); } - static if(hasReturnValue) - return pushReturnValues(L, ret); - else - return 0; + return 0; } // Ditto, but wrap the try-catch in a nested function because the return value's // declaration and initialization cannot be separated. -int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) - if(is(BindableReturnType!T == const) || - is(BindableReturnType!T == immutable)) +int callFunction(T, RT = BindableReturnType!T)(lua_State* L, T func, ParameterTypeTuple!T args) + if((!returnsRef!T || !isUserStruct!RT) && + (is(RT == const) || is(RT == immutable))) { + // TODO: reconsider if this is necessary? + // surely it would be easier just to wrap the return statement in the try? auto ref call() { try @@ -131,20 +148,20 @@ int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) return pushReturnValues(L, call()); } -private: +package: // TODO: right now, virtual functions on specialized classes can be called with base classes as 'self', not safe! -extern(C) int methodWrapper(T, Class, bool virtual)(lua_State* L) +extern(C) int methodWrapper(M, T, bool virtual)(lua_State* L) { - alias ParameterTypeTuple!T Args; + alias Args = ParameterTypeTuple!M; - static assert ((variadicFunctionStyle!T != Variadic.d && variadicFunctionStyle!T != Variadic.c), + static assert ((variadicFunctionStyle!M != Variadic.d && variadicFunctionStyle!M != Variadic.c), "Non-typesafe variadic functions are not supported."); //Check arguments int top = lua_gettop(L); - static if (variadicFunctionStyle!T == Variadic.typesafe) + static if (variadicFunctionStyle!M == Variadic.typesafe) enum requiredArgs = Args.length; else enum requiredArgs = Args.length + 1; @@ -152,42 +169,54 @@ extern(C) int methodWrapper(T, Class, bool virtual)(lua_State* L) if(top < requiredArgs) argsError(L, top, requiredArgs); - Class self = *cast(Class*)luaL_checkudata(L, 1, toStringz(Class.mangleof)); + static if(is(T == struct)) + Ref!T self = *cast(Ref!T*)luaL_checkudata(L, 1, toStringz(T.mangleof)); + else + T self = *cast(T*)luaL_checkudata(L, 1, toStringz(T.mangleof)); static if(virtual) { - alias ReturnType!T function(Class, Args) VirtualWrapper; + alias RT = InOutReturnType!(M.init, T); + static if(returnsRef!M && isUserStruct!RT) + alias VirtualWrapper = ref RT function(T, Args); + else + alias VirtualWrapper = RT function(T, Args); VirtualWrapper func = cast(VirtualWrapper)lua_touserdata(L, lua_upvalueindex(1)); } else { - T func; - func.ptr = cast(void*)self; + M func; + static if(is(T == struct)) + func.ptr = cast(void*)&self.__instance(); + else + func.ptr = cast(void*)self; func.funcptr = cast(typeof(func.funcptr))lua_touserdata(L, lua_upvalueindex(1)); } //Assemble arguments static if(virtual) { - ParameterTypeTuple!VirtualWrapper allArgs; + TreatArgs!(ParameterTypeTuple!VirtualWrapper) allArgs; allArgs[0] = self; - alias allArgs[1..$] args; + alias args = allArgs[1..$]; } else { - Args allArgs; - alias allArgs args; + // TODO: maybe we should build a tuple of 'ReturnType!(getArgument!(T, i))' for each arg? + // then we could get rid of this TreatArgs! rubbish... + TreatArgs!Args allArgs; + alias args = allArgs; } foreach(i, Arg; Args) - args[i] = getArgument!(T, i)(L, i + 2); + args[i] = getArgument!(M, i)(L, i + 2); return callFunction!(typeof(func))(L, func, allArgs); } extern(C) int functionWrapper(T)(lua_State* L) { - alias FillableParameterTypeTuple!T Args; + alias Args = FillableParameterTypeTuple!T; static assert ((variadicFunctionStyle!T != Variadic.d && variadicFunctionStyle!T != Variadic.c), "Non-typesafe variadic functions are not supported."); @@ -210,19 +239,13 @@ extern(C) int functionWrapper(T)(lua_State* L) T func = *cast(T*)lua_touserdata(L, lua_upvalueindex(1)); //Assemble arguments - Args args; + TreatArgs!Args args; foreach(i, Arg; Args) args[i] = getArgument!(T, i)(L, i + 1); return callFunction!T(L, func, args); } -extern(C) int functionCleaner(lua_State* L) -{ - GC.removeRoot(lua_touserdata(L, 1)); - return 0; -} - public: void pushFunction(T)(lua_State* L, T func) if (isSomeFunction!T) @@ -238,7 +261,7 @@ void pushFunction(T)(lua_State* L, T func) if (isSomeFunction!T) if(luaL_newmetatable(L, "__dcall") == 1) { - lua_pushcfunction(L, &functionCleaner); + lua_pushcfunction(L, &userdataCleaner); lua_setfield(L, -2, "__gc"); } @@ -249,18 +272,37 @@ void pushFunction(T)(lua_State* L, T func) if (isSomeFunction!T) } // TODO: optimize for non-virtual functions -void pushMethod(Class, string member)(lua_State* L) if (isSomeFunction!(__traits(getMember, Class, member))) +void pushMethod(T, string member)(lua_State* L) if (isSomeFunction!(__traits(getMember, T, member))) { - alias typeof(mixin("&Class.init." ~ member)) T; + alias M = typeof(mixin("&T.init." ~ member)); + + enum isVirtual = !is(T == struct); // TODO: final methods should also be handled... - // Delay vtable lookup until the right time - static ReturnType!T virtualWrapper(Class self, ParameterTypeTuple!T args) + static if(isVirtual) { - return mixin("self." ~ member)(args); + alias RT = InOutReturnType!(mixin("T." ~ member), T); + + // Delay vtable lookup until the right time + static if(returnsRef!M && isUserStruct!RT) + { + static ref RT virtualWrapper(T self, ParameterTypeTuple!M args) + { + return mixin("self." ~ member)(args); + } + } + else + { + static RT virtualWrapper(T self, ParameterTypeTuple!M args) + { + return mixin("self." ~ member)(args); + } + } + lua_pushlightuserdata(L, &virtualWrapper); } + else + lua_pushlightuserdata(L, mixin("&T.init." ~ member).funcptr); - lua_pushlightuserdata(L, &virtualWrapper); - lua_pushcclosure(L, &methodWrapper!(T, Class, true), 1); + lua_pushcclosure(L, &methodWrapper!(M, T, isVirtual), 1); } /** diff --git a/luad/conversions/helpers.d b/luad/conversions/helpers.d new file mode 100644 index 0000000..7f05a35 --- /dev/null +++ b/luad/conversions/helpers.d @@ -0,0 +1,341 @@ +/** +Various helper functions, templates, and common code used by the conversion routines. +*/ +module luad.conversions.helpers; + +import luad.conversions.functions; + +import luad.c.all; +import luad.all; + +import core.memory; + +import std.traits; + +package: + +// resolves the proper return type for functions that return inout(T) +template InOutReturnType(alias func, T) +{ + alias InOutReturnType = typeof((){ + ReturnType!func function(inout Unqual!T) f; + T t; + return f(t); + }()); +} + +// Note: should we only consider @property functions? +enum isGetter(alias m) = !is(ReturnType!m == void) && ParameterTypeTuple!(m).length == 0;// && isProperty!m; +enum isSetter(alias m) = is(ReturnType!m == void) && ParameterTypeTuple!(m).length == 1;// && isProperty!m; + +template GetterType(T, string member) +{ + // TODO: parse the overloads, find the getter, ie, function matching T() (only allow @property?) + // (currently this only works if the getter appears first) + static if(isProperty!(T, member)) + alias GetterType = InOutReturnType!(mixin("T."~member), T); + else + alias GetterType = typeof(mixin("T."~member)); +} + +template SetterTypes(T, string member) +{ + // find setter overloads + template Impl(Overloads...) + { + static if(Overloads.length == 0) + alias Impl = TypeTuple!(); + else static if(isSetter!(Overloads[0])) + alias Impl = TypeTuple!(ParameterTypeTuple!(Overloads[0])[0], Impl!(Overloads[1..$])); + else + alias Impl = TypeTuple!(Impl!(Overloads[1..$])); + } + + // TODO: do all overloads need to be properties? + // perhaps this should be changed to isMemberFunction, and add an isProperty filter to isSetter? + static if(isProperty!(T, member)) + alias SetterTypes = Impl!(__traits(getOverloads, T, member)); + else + alias SetterTypes = TypeTuple!(typeof(mixin("T."~member))); +} + +template MethodsExclusingProperties(T, string member) +{ + // TODO: this should be used when populating methods. it should filter out getter/setter overloads + alias MethodsExclusingProperties = Alias!(__traits(getOverloads, T, member)); +} + +struct Ref(T) +{ + alias __instance this; + + this(ref T s) { ptr = &s; } + + @property ref T __instance() { return *ptr; } + +private: + T* ptr; +} + +alias AliasMember(T, string member) = Alias!(__traits(getMember, T, member)); + +enum isInternal(string field) = field.length >= 2 && field[0..2] == "__"; +enum isMemberFunction(T, string member) = mixin("is(typeof(&T.init." ~ member ~ ") == delegate)"); +enum isUserStruct(T) = is(T == struct) && !is(T == LuaObject) && !is(T == LuaTable) && !is(T == LuaDynamic) && !is(T == LuaFunction) && !is(T == Ref!S, S); +enum isValueType(T) = isUserStruct!T || isStaticArray!T; + +enum canRead(T, string member) = mixin("__traits(compiles, (T* a) => a."~member~")"); +template canCall(T, string member) +{ + // TODO: this is neither robust, nor awesome. surely there is a better way than this...? + static if(mixin("is(typeof(T."~member~") == const)")) + enum canCall = !is(T == shared); + else static if(mixin("is(typeof(T."~member~") == immutable)")) + enum canCall = is(T == immutable); + else static if(mixin("is(typeof(T."~member~") == shared)")) + enum canCall = is(T == shared); + else + enum canCall = !is(T == const) && !is(T == immutable) && !is(T == shared); +} +// TODO: in the presence of a setter property with no getter, '= typeof(T.member).init' doesn't work +// we need to use the setter's argument type instead... +enum canWrite(T, string member) = mixin("__traits(compiles, (cast(T*)null)."~member~" = typeof(T."~member~").init)"); + +template isOperator(string field) +{ + enum isOperator = field == "toString" || + field == "toHash" || + field == "opEquals" || + field == "opCmp" || + field == "opCall" || + field == "opUnary" || + field == "opBinary" || + field == "opBinaryRight" || + field == "opAssign" || + field == "opOpAssign" || + field == "opDispatch"; +} + +template isProperty(T, string member) +{ + static if(isMemberFunction!(T, member)) + enum isProperty = functionAttributes!(mixin("T.init." ~ member)) & FunctionAttribute.property; + else + enum isProperty = false; +} + +template skipMember(T, string member) +{ + static if(isInternal!member || + isOperator!member || + member == "this" || + mixin("is(T."~member~")") || + __traits(getProtection, __traits(getMember, T, member)) != "public") + enum skipMember = true; + else + enum skipMember = false; +} + +template returnsRef(F...) +{ + static if(isSomeFunction!(F[0])) + enum returnsRef = !!(functionAttributes!(F[0]) & FunctionAttribute.ref_); + else + enum returnsRef = false; +} + + +void pushGetter(T, string member)(lua_State* L) +{ + alias RT = GetterType!(T, member); + + static if(is(T == class)) + { + final class X + { + 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); + } + } + } + } + else + { + struct X + { + 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, (&X.init.get).funcptr); + lua_pushcclosure(L, &methodWrapper!(typeof(&X.init.get), T, false), 1); +} + +void pushSetter(T, string member)(lua_State* L) +{ + alias OverloadTypes = SetterTypes!(T, member); + static assert(OverloadTypes.length, T.stringof~"."~member~": no setters?! shouldn't be here..."); + + // TODO: This is broken if there are setter overloads, we need to support overloads eventually... + static if(OverloadTypes.length > 1) + pragma(msg, T.stringof~"."~member~" has overloaded setter: "~OverloadTypes.stringof); + alias ArgType = OverloadTypes[0]; + + static if(is(T == class)) + { + final class X + { + static if(isUserStruct!ArgType) + { + final void set(ref ArgType value) + { + T _this = *cast(T*)&this; + mixin("_this."~member) = value; + } + } + else + { + final void set(ArgType value) + { + T _this = *cast(T*)&this; + mixin("_this."~member) = value; + } + } + } + } + else + { + struct X + { + static if(isUserStruct!ArgType) + { + void set(ref ArgType value) + { + T* _this = cast(T*)&this; + mixin("_this."~member) = value; + } + } + else + { + void set(ArgType value) + { + T* _this = cast(T*)&this; + mixin("_this."~member) = value; + } + } + } + } + + lua_pushlightuserdata(L, (&X.init.set).funcptr); + lua_pushcclosure(L, &methodWrapper!(typeof(&X.init.set), T, false), 1); +} + + +// 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; +} + +void verifyType(T)(lua_State* L, int idx) +{ + 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: support pointers... + + // TODO: if is(T == const), then we need to check __dmangle == T, const(T) or immutable(T) + size_t manglelen; + auto cmangle = lua_tolstring(L, -1, &manglelen); + if(cmangle[0 .. manglelen] != T.mangleof) + { + lua_getfield(L, -2, "__dtype"); + 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 +} + + +extern(C) int userdataCleaner(lua_State* L) +{ + GC.removeRoot(lua_touserdata(L, 1)); + return 0; +} + +extern(C) int index(lua_State* L) +{ + auto field = lua_tostring(L, 2); + + // check the getter table + lua_getfield(L, lua_upvalueindex(1), field); + if(!lua_isnil(L, -1)) + { + lua_pushvalue(L, 1); + lua_call(L, 1, LUA_MULTRET); + return lua_gettop(L) - 2; + } + else + lua_pop(L, 1); + + // return method + lua_getfield(L, lua_upvalueindex(2), field); + return 1; +} + +extern(C) int newIndex(lua_State* L) +{ + auto field = lua_tostring(L, 2); + + // call setter + lua_getfield(L, lua_upvalueindex(1), field); + if(!lua_isnil(L, -1)) + { + lua_pushvalue(L, 1); + lua_pushvalue(L, 3); + lua_call(L, 2, LUA_MULTRET); + } + else + { + // TODO: error? + } + + return 0; +} diff --git a/luad/conversions/structs.d b/luad/conversions/structs.d index 6da6164..4fc4291 100644 --- a/luad/conversions/structs.d +++ b/luad/conversions/structs.d @@ -1,71 +1,157 @@ /** -Internal module for pushing and getting _structs. - -A struct is treated as a table layout schema. -Pushing a struct to Lua will create a table and fill it with key-value pairs - corresponding to struct fields - from the struct; the field name becomes the table key as a string. -Struct methods are treated as if they were delegate fields pointing to the method. +Internal module for pushing and getting structs. +Structs are handled by-value across the LuaD API boundary, but internally managed by reference, with semantics equivalent to tables. +Fields and properties are handled via a thin shim implemented in __index/__newindex. Methods are registered directly. +mutable, const and immutable are all supported as expected. immutable structs will capture a direct reference to the D instance, and not be duplicated by LuaD. For an example, see the "Configuration File" example on the $(LINK2 $(REFERENCETOP),front page). */ module luad.conversions.structs; -import luad.c.all; +import luad.conversions.helpers; +import luad.conversions.functions; +import luad.c.all; import luad.stack; -private template isInternal(string field) -{ - enum isInternal = field.length >= 2 && field[0..2] == "__"; -} +import core.memory; -//TODO: ignore static fields, post-blits, destructors, etc? -void pushStruct(T)(lua_State* L, ref T value) if (is(T == struct)) +import std.traits; +import std.conv; + + +private void pushGetters(T)(lua_State* L) { - lua_createtable(L, 0, value.tupleof.length); + lua_newtable(L); // -2 is getters + lua_newtable(L); // -1 is methods - foreach(field; __traits(allMembers, T)) + // populate getters + foreach(member; __traits(allMembers, T)) { - static if(!isInternal!field && - field != "this" && - field != "opAssign") + static if(!skipMember!(T, member) && + !isStaticMember!(T, member)) { - pushValue(L, field); + 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); + } + } + } - enum isMemberFunction = mixin("is(typeof(&value." ~ field ~ ") == delegate)"); + lua_pushcclosure(L, &index, 2); +} - static if(isMemberFunction) - pushValue(L, mixin("&value." ~ field)); - else - pushValue(L, mixin("value." ~ field)); +private void pushSetters(T)(lua_State* L) +{ + lua_newtable(L); - lua_settable(L, -3); + // populate setters + foreach(member; __traits(allMembers, T)) + { + static if(!skipMember!(T, member) && + !isStaticMember!(T, member) && + canWrite!(T, member)) // TODO: move into the setter for readonly fields + { + static if(!isMemberFunction!(T, member) || isProperty!(T, member)) + { + pushSetter!(T, member)(L); + lua_setfield(L, -2, member.ptr); + } } } + + lua_pushcclosure(L, &newIndex, 1); } -T getStruct(T)(lua_State* L, int idx) if(is(T == struct)) +private void pushMeta(T)(lua_State* L) { - T s; - fillStruct(L, idx, s); - return s; + if(luaL_newmetatable(L, T.mangleof.ptr) == 0) + return; + + 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, &userdataCleaner); + lua_setfield(L, -2, "__gc"); + + pushGetters!T(L); + lua_setfield(L, -2, "__index"); + pushSetters!T(L); + lua_setfield(L, -2, "__newindex"); + + static if(__traits(hasMember, T, "toString")) + { + pushMethod!(T, "toString")(L); + lua_setfield(L, -2, "__tostring"); + } + + static if(__traits(hasMember, T, "opEquals")) + { + pushMethod!(T, "opEquals")(L); + lua_setfield(L, -2, "__eq"); + } + // TODO: __lt,__le (wrap opCmp) + + // TODO: operators, etc... + + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__metatable"); } -void fillStruct(T)(lua_State* L, int idx, ref T s) if(is(T == struct)) +void pushStruct(T)(lua_State* L, ref T value) if (is(T == struct)) { - foreach(field; __traits(allMembers, T)) + // if T is immutable, we can capture a reference, otherwise we need to take a copy + static if(is(T == immutable)) // TODO: verify that this is actually okay? { - static if(field != "this" && !isInternal!(field)) - { - static if(__traits(getOverloads, T, field).length == 0) - { - lua_getfield(L, idx, field.ptr); - if(lua_isnil(L, -1) == 0) { - mixin("s." ~ field ~ - " = popValue!(typeof(s." ~ field ~ "))(L);"); - } else - lua_pop(L, 1); - } - } + auto udata = cast(Ref!T*)lua_newuserdata(L, Ref!T.sizeof); + *udata = Ref!T(value); } + else + { + Ref!T* udata = cast(Ref!T*)lua_newuserdata(L, Ref!T.sizeof); + // TODO: we should try and call the postblit here maybe...? +// T* copy = new T(value); +// T* copy = std.conv.emplace(cast(T*)GC.malloc(T.sizeof), value); + Unqual!T* copy = cast(Unqual!T*)GC.malloc(T.sizeof); + *copy = value; + *udata = Ref!T(*copy); + } + + GC.addRoot(udata); + + pushMeta!T(L); + lua_setmetatable(L, -2); +} + +void pushStruct(R : Ref!T, T)(lua_State* L, R value) if (is(T == struct)) +{ + auto udata = cast(Ref!T*)lua_newuserdata(L, Ref!T.sizeof); + *udata = Ref!T(value); + + GC.addRoot(udata); + + pushMeta!T(L); + lua_setmetatable(L, -2); +} + +ref T getStruct(T)(lua_State* L, int idx) if(is(T == struct)) +{ + verifyType!T(L, idx); + + Ref!T* udata = cast(Ref!T*)lua_touserdata(L, idx); + return *udata; } version(unittest) diff --git a/luad/stack.d b/luad/stack.d index d1cf6f5..95ab0d6 100644 --- a/luad/stack.d +++ b/luad/stack.d @@ -72,6 +72,7 @@ import luad.conversions.structs; import luad.conversions.assocarrays; import luad.conversions.classes; import luad.conversions.variant; +import luad.conversions.helpers; /** * Push a value of any type to the stack. @@ -79,7 +80,7 @@ import luad.conversions.variant; * L = stack to push to * value = value to push */ -void pushValue(T)(lua_State* L, T value) +void pushValue(T)(lua_State* L, T value) if(!isUserStruct!T) { static if(is(T : LuaObject)) value.push(); @@ -120,7 +121,7 @@ void pushValue(T)(lua_State* L, T value) else static if(isArray!T) pushArray(L, value); - else static if(is(T == struct)) + else static if(is(T == Ref!S, S) && isUserStruct!S) pushStruct(L, value); // luaCFunction's are directly pushed @@ -142,6 +143,18 @@ void pushValue(T)(lua_State* L, T value) static assert(false, "Unsupported type `" ~ T.stringof ~ "` in stack push operation"); } +void pushValue(T)(lua_State* L, ref T value) if(isUserStruct!T) +{ + static if(isArray!T) + pushArray(L, value); + else static if(is(T == struct)) + pushStruct(L, value); + else + { + static assert(false, "Shouldn't be here! `" ~ T.stringof ~ "` should be handled by the other overload."); + } +} + template isVoidArray(T) { enum isVoidArray = is(T == void[]) || @@ -172,10 +185,10 @@ template luaTypeOf(T) else static if(isSomeFunction!T || is(T == LuaFunction)) enum luaTypeOf = LUA_TFUNCTION; - else static if(isArray!T || isAssociativeArray!T || is(T == struct) || is(T == LuaTable)) + else static if(isArray!T || isAssociativeArray!T || is(T == LuaTable)) enum luaTypeOf = LUA_TTABLE; - else static if(is(T : Object)) + else static if(is(T : const(Object)) || is(T == struct)) enum luaTypeOf = LUA_TUSERDATA; else @@ -202,7 +215,7 @@ private void argumentTypeMismatch(lua_State* L, int idx, int expectedType) * L = stack to get from * idx = value stack index */ -T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int idx) +T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int idx) if(!isUserStruct!T) { debug //ensure unchanged stack { @@ -223,7 +236,7 @@ T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int enum expectedType = luaTypeOf!T; //if a class reference, return null for nil values - static if(is(T : Object)) + static if(is(T : const(Object))) { if(type == LuaType.Nil) return null; @@ -288,13 +301,11 @@ T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int return getVariant!T(L, idx); } - else static if(is(T == struct)) - return getStruct!T(L, idx); else static if(isSomeFunction!T) return getFunction!T(L, idx); - else static if(is(T : Object)) + else static if(is(T : const(Object))) return getClassInstance!T(L, idx); else @@ -303,11 +314,49 @@ T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int } } +// we need an overload that handles struct and static arrays (which need to return by ref) +ref T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int idx) if(isUserStruct!T) +{ + debug //ensure unchanged stack + { + int _top = lua_gettop(L); + scope(success) assert(lua_gettop(L) == _top); + } + + // TODO: confirm that we need this in this overload...? + static if(!is(T == LuaObject) && !is(T == LuaDynamic) && !isVariant!T) + { + int type = lua_type(L, idx); + enum expectedType = luaTypeOf!T; + + //if a class reference, return null for nil values + static if(is(T : const(Object))) + { + if(type == LuaType.Nil) + return null; + } + + if(type != expectedType) + typeMismatchHandler(L, idx, expectedType); + } + + static if(isArray!T) + return getArray!T(L, idx); + + else static if(is(T == struct)) + return getStruct!T(L, idx); + + else + { + static assert(false, "Shouldn't be here! `" ~ T.stringof ~ "` should be handled by the other overload."); + } +} + /** * Same as calling getValue!(T, typeMismatchHandler)(L, -1), then popping one value from the stack. * See_Also: $(MREF getValue) */ -T popValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L) +auto ref popValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L) { scope(success) lua_pop(L, 1); return getValue!(T, typeMismatchHandler)(L, -1); @@ -374,7 +423,13 @@ auto getArgument(T, int narg)(lua_State* L, int idx) return cstr[0 .. len]; } else - return getValue!(Arg, argumentTypeMismatch)(L, idx); + { + // TODO: make an overload to handle struct and static array, and remove this Ref! hack? + static if(isUserStruct!Arg) // user struct's need to return wrapped in a Ref + return Ref!Arg(getValue!(Arg, argumentTypeMismatch)(L, idx)); + else + return getValue!(Arg, argumentTypeMismatch)(L, idx); + } } template isVariableReturnType(T : LuaVariableReturn!U, U) @@ -477,7 +532,7 @@ int pushReturnValues(T)(lua_State* L, T value) pushTuple(L, value); return cast(int)T.Types.length; } - else static if(isStaticArray!T) + else static if(isStaticArray!T) // TODO: remove this special case when we fix pushValue for static arrays { pushStaticArray(L, value); return cast(int)value.length; From b26fff1af505c8a63f8b8737aecf812a00f50318 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 19 Aug 2014 02:45:50 +1000 Subject: [PATCH 2/2] Added @noscript --- luad/base.d | 5 +++++ luad/conversions/helpers.d | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/luad/base.d b/luad/base.d index 8bc5ddb..826e3a8 100644 --- a/luad/base.d +++ b/luad/base.d @@ -5,6 +5,11 @@ import luad.stack; import core.stdc.string : strlen; + +// shall we declare the attributes here? +struct noscript {} + + /** * Enumerates all Lua types. */ diff --git a/luad/conversions/helpers.d b/luad/conversions/helpers.d index 7f05a35..d2b8f0b 100644 --- a/luad/conversions/helpers.d +++ b/luad/conversions/helpers.d @@ -11,6 +11,7 @@ import luad.all; import core.memory; import std.traits; +import std.typetuple; package: @@ -133,7 +134,7 @@ template skipMember(T, string member) __traits(getProtection, __traits(getMember, T, member)) != "public") enum skipMember = true; else - enum skipMember = false; + enum skipMember = hasAttribute!(__traits(getMember, T, member), noscript) >= 0; } template returnsRef(F...) @@ -144,6 +145,41 @@ template returnsRef(F...) enum returnsRef = false; } +template hasAttribute(alias x, alias attr) +{ + template typeImpl(int i, A...) + { + static if(A.length == 0) + enum typeImpl = -1; + else static if(is(A[0])) + enum typeImpl = is(A[0] == attr) ? i : typeImpl!(i+1, A[1..$]); + else + enum typeImpl = is(typeof(A[0]) == attr) ? i : typeImpl!(i+1, A[1..$]); + } + template valImpl(int i, A...) + { + static if(A.length == 0) + enum valImpl = -1; + else static if(is(A[0]) || !is(typeof(A[0]) : typeof(attr))) + enum valImpl = valImpl!(i+1, A[1..$]); + else + enum valImpl = A[0] == attr ? i : valImpl!(i+1, A[1..$]); + } + static if(is(attr)) + enum hasAttribute = typeImpl!(0, __traits(getAttributes, x)); + else + enum hasAttribute = valImpl!(0, __traits(getAttributes, x)); +} + +template getAttribute(alias x, size_t i) +{ + alias Attrs = TypeTuple!(__traits(getAttributes, x)); + static if(is(Attrs[i])) + alias getAttribute = TypeTuple!(Attrs[i]); + else + enum getAttribute = TypeTuple!(Attrs[i]); +} + void pushGetter(T, string member)(lua_State* L) {