From 25b2bcd6a2188c0aa6bdda812e16abc17df8d186 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 19 Aug 2014 00:25:33 +1000 Subject: [PATCH 1/8] Added support for enum's. Enums are held in Lua as strings. No support for enum values that are not valid keys. No support for bitfields (yet?). Conversion function performance could be improved (linear search >_<). --- luad/conversions/enums.d | 173 +++++++++++++++++++++++++++++++++++++++ luad/stack.d | 12 ++- luad/state.d | 1 + visuald/LuaD.visualdproj | 1 + 4 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 luad/conversions/enums.d diff --git a/luad/conversions/enums.d b/luad/conversions/enums.d new file mode 100644 index 0000000..974861f --- /dev/null +++ b/luad/conversions/enums.d @@ -0,0 +1,173 @@ +/** +Internal module for pushing and getting _enums. + +Enum's are treated in Lua as strings. + +Conversion of enum keys is case-insensitive, which I think is more useful for Lua's typical 'config' style usage + +This is still a work-in-progress. Outstanding issues include: + Handling of bitfields. + Assignment of integer keys? + Conversion function needs to be improved (linear search! >_<) +*/ +module luad.conversions.enums; + +import luad.c.all; +import luad.stack; + +import std.traits; +import std.conv; +import std.range; +import std.string; + +struct KeyValuePair(ValueType) +{ + string key; + ValueType value; +} + +// produce a tuple of KeyValuePair's for an enum. +template EnumKeyValuePair(Enum) +{ + template impl(size_t len, size_t offset, Items...) + { + static if(offset == len) + alias impl = TypeTuple!(); + else + alias impl = TypeTuple!(KeyValuePair!Enum(Items[offset], Items[len + offset]), impl!(len, offset + 1, Items)); + } + + alias Keys = TypeTuple!(__traits(allMembers, Enum)); + alias Values = EnumMembers!Enum; + static assert(Keys.length == Values.length); + + alias EnumKeyValuePair = impl!(Keys.length, 0, TypeTuple!(Keys, Values)); +} + +immutable(KeyValuePair!Enum)[] getKeyValuePairs(Enum)() pure nothrow @nogc +{ + static immutable(KeyValuePair!Enum[]) kvp = [ EnumKeyValuePair!Enum ]; + return kvp; +} + +// TODO: These linear lookups are pretty crappy... we can do better, but this get's us working. +Enum getEnumValue(Enum)(lua_State* L, const(char)[] value) if(is(Enum == enum)) +{ + value = value.strip; + if(!value.empty) + { + auto kvp = getKeyValuePairs!Enum(); + foreach(ref i; kvp) + { + if(!icmp(i.key, value)) // case inseneitive enum keys... + return i.value; + } + } + luaL_error(L, "invalid enum key '%s' for enum type %s", value.ptr, Enum.stringof.ptr); + return Enum.init; +} + +string getEnumFromValue(Enum)(Enum value) +{ + auto kvp = getKeyValuePairs!Enum(); + foreach(ref i; kvp) + { + if(value == i.value) + return i.key; + } + return null; +} + +void pushEnum(T)(lua_State* L, T value) if (is(T == enum)) +{ + string key = getEnumFromValue(value); + if(key) + lua_pushlstring(L, key.ptr, key.length); + else + luaL_error(L, "invalid value for enum type %s", T.stringof.ptr); +} + +T getEnum(T)(lua_State* L, int idx) if(is(T == enum)) +{ + // TODO: check to see if idx is a number, if it is, convert it directly? + + size_t len; + const(char)* s = lua_tolstring(L, idx, &len); + return getEnumValue!T(L, s[0..len]); +} + +void pushStaticTypeInterface(T)(lua_State* L) if(is(T == enum)) +{ + lua_newtable(L); + + // TODO: we could get fancy and make an __index table of keys, so that they are read-only + // ... but for now, we'll just populate a table with the keys as strings + + // set 'init' + string initVal = getEnumFromValue(T.init); + lua_pushlstring(L, initVal.ptr, initVal.length); + lua_setfield(L, -2, "init"); + + // TODO: integral enums also have 'min' and 'max' + + // we'll create tables for the keys and valyes arrays. + lua_newtable(L); // keys + lua_newtable(L); // values + + // add the enum keys + auto kvp = getKeyValuePairs!T(); + foreach(int i, ref e; kvp) + { + // set the key to the key string (lua will carry enums by string) + lua_pushlstring(L, e.key.ptr, e.key.length); + lua_setfield(L, -4, e.key.ptr); + + // push the key to the keys array + lua_pushlstring(L, e.key.ptr, e.key.length); + lua_rawseti(L, -3, i+1); + + // push the value to the values array + pushValue!(OriginalType!T)(L, e.value); + lua_rawseti(L, -2, i+1); + } + + lua_setfield(L, -3, "values"); + lua_setfield(L, -2, "keys"); +} + +version(unittest) +{ + import luad.base; + + enum E + { + Key0, + Key1 + } +} + +unittest +{ + import luad.testing; + + lua_State* L = luaL_newstate(); + scope(success) lua_close(L); + luaL_openlibs(L); + + pushValue(L, E.Key0); + assert(lua_isstring(L, -1)); + lua_setglobal(L, "enum"); + + unittest_lua(L, ` + assert(enum == "Key0") + + enum = "key1" + `); + + lua_getglobal(L, "enum"); + E e = getValue!E(L, -1); + assert(e == E.Key1); + lua_pop(L, 1); + + // TODO: test the enum type interface... +} diff --git a/luad/stack.d b/luad/stack.d index d1cf6f5..8f0eeb5 100644 --- a/luad/stack.d +++ b/luad/stack.d @@ -71,6 +71,7 @@ import luad.conversions.arrays; import luad.conversions.structs; import luad.conversions.assocarrays; import luad.conversions.classes; +import luad.conversions.enums; import luad.conversions.variant; /** @@ -90,6 +91,9 @@ void pushValue(T)(lua_State* L, T value) else static if(is(T == Nil)) lua_pushnil(L); + else static if(is(T == enum)) + pushEnum(L, value); + else static if(is(T == bool)) lua_pushboolean(L, cast(bool)value); @@ -157,7 +161,10 @@ template isVoidArray(T) */ template luaTypeOf(T) { - static if(is(T == bool)) + static if(is(T == enum)) + enum luaTypeOf = LUA_TSTRING; + + else static if(is(T == bool)) enum luaTypeOf = LUA_TBOOLEAN; else static if(is(T == Nil)) @@ -251,6 +258,9 @@ T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int else static if(is(T == Nil)) return nil; + else static if(is(T == enum)) + return getEnum!T(L, idx); + else static if(is(T == bool)) return lua_toboolean(L, idx); diff --git a/luad/state.d b/luad/state.d index d58757a..6c350bc 100644 --- a/luad/state.d +++ b/luad/state.d @@ -8,6 +8,7 @@ import std.typecons : isTuple; import luad.c.all; import luad.stack; import luad.conversions.classes; +import luad.conversions.enums; import luad.base, luad.table, luad.lfunction, luad.dynamic, luad.error; diff --git a/visuald/LuaD.visualdproj b/visuald/LuaD.visualdproj index 8218571..ce04125 100644 --- a/visuald/LuaD.visualdproj +++ b/visuald/LuaD.visualdproj @@ -296,6 +296,7 @@ + From 409f91efab31ee9ea308f57ee5775bc52243725b Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 18 Aug 2014 03:12:12 +1000 Subject: [PATCH 2/8] 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 | 264 +++++++++++++++++++++------ luad/conversions/variant.d | 3 +- luad/stack.d | 79 ++++++-- visuald/LuaD.visualdproj | 1 + 7 files changed, 825 insertions(+), 208 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..b215f82 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) @@ -73,12 +159,33 @@ version(unittest) import luad.base; struct S { + struct C + { + int i; + } + LuaObject o; int i; double n; string s; - string f(){ return "foobar"; } + enum e = "enum"; + + C c; + + string f() { return "foobar"; } + + @property string p() { return prop; } + @property void p(string v) { prop = v; } + + @property inout(C) io() inout { return c; } + @property void io(C v) { return c = v; } + + @property ref C r() { return c; } + @property void r(ref C v) { return c = v; } + + protected: + string prop = "getter"; } } @@ -93,45 +200,82 @@ unittest pushValue(L, "test"); auto obj = popValue!LuaObject(L); - pushValue(L, S(obj, 1, 2.3, "hello")); - assert(lua_istable(L, -1)); + auto s = S(obj, 1, 2.3, "hello", S.I(10)); + pushValue(L, s); + assert(lua_isuserdata(L, -1)); lua_setglobal(L, "struct"); unittest_lua(L, ` - for key, expected in pairs{i = 1, n = 2.3, s = "hello"} do + for key, expected in pairs{i = 1, n = 2.3, s = "hello", e = "enum", p = "getter"} do local value = struct[key] assert(value == expected, ("bad table pair: '%s' = '%s' (expected '%s')"):format(key, value, expected) ) end - assert(struct.f() == "foobar") + assert(struct:f() == "foobar") + assert(struct.c.i == 10) + + -- test member struct + struct.c.i = 20 + assert(struct.c.i == 20) + + -- test property, return by value + struct.io.i = 30 + assert(struct.io.i == 20) + local l = struct.io + l.i = 30 + struct.io = l + assert(struct.io.i == 30) + + -- test property, return by ref + struct.r.i = 40 + assert(struct.r.i == 40) + l.i = 50 + struct.r = l + assert(struct.r.i == 50) + + -- set some values to return to the D code + struct.i = 2 + struct.n = 4.6 + struct.s = "world" + struct.c.i = 100 + struct.p = "setter" `); lua_getglobal(L, "struct"); - S s = getStruct!S(L, -1); + s = getValue!S(L, -1); assert(s.o == obj); - assert(s.i == 1); - assert(s.n == 2.3); - assert(s.s == "hello"); + assert(s.i == 2); + assert(s.n == 4.6); + assert(s.s == "world"); + assert(s.i.i == 100); + assert(s.p == "setter"); lua_pop(L, 1); - struct S2 - { - string a, b; - } +/+ + // TODO: test the type interface + // test constructor works unittest_lua(L, ` - incompleteStruct = {a = "foo"} + struct = S("test", 2, 4.6, "world") `); - lua_getglobal(L, "incompleteStruct"); - S2 s2 = getStruct!S2(L, -1); + lua_getglobal(L, "struct"); + s = getValue!S(L, -1); - assert(s2.a == "foo"); - assert(s2.b == null); + assert(s.o.to!string == "test"); + assert(s.i == 2); + assert(s.n == 4.6); + assert(s.s == "world"); lua_pop(L, 1); + + // test assigning member structs + + // test static variables work + // test static methods work ++/ } diff --git a/luad/conversions/variant.d b/luad/conversions/variant.d index 36902dd..a7567ed 100644 --- a/luad/conversions/variant.d +++ b/luad/conversions/variant.d @@ -110,7 +110,8 @@ unittest void f(){} } - pushValue(L, Algebraic!(S, int)(S(1, 2.3, "hello"))); + auto s = Algebraic!(S, int)(S(1, 2.3, "hello")); + pushValue(L, s); assert(lua_istable(L, -1)); lua_setglobal(L, "struct"); diff --git a/luad/stack.d b/luad/stack.d index 8f0eeb5..d8ff371 100644 --- a/luad/stack.d +++ b/luad/stack.d @@ -73,6 +73,7 @@ import luad.conversions.assocarrays; import luad.conversions.classes; import luad.conversions.enums; import luad.conversions.variant; +import luad.conversions.helpers; /** * Push a value of any type to the stack. @@ -80,7 +81,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(); @@ -124,7 +125,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 @@ -146,6 +147,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[]) || @@ -179,10 +192,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 @@ -209,7 +222,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 { @@ -230,7 +243,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; @@ -298,13 +311,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 @@ -313,11 +324,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); @@ -384,7 +433,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) @@ -487,7 +542,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; diff --git a/visuald/LuaD.visualdproj b/visuald/LuaD.visualdproj index ce04125..b4ee7b0 100644 --- a/visuald/LuaD.visualdproj +++ b/visuald/LuaD.visualdproj @@ -298,6 +298,7 @@ + From 3a56818065a60f6bf146220c72e501547ed93662 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 30 Aug 2014 01:03:34 +1000 Subject: [PATCH 3/8] Added support for pointers. Pointers are opaque. Pointers support 'nil' assignment. 'deref' property on pointers to access the actual object. --- luad/conversions/pointers.d | 145 ++++++++++++++++++++++++++++++++++++ luad/stack.d | 14 +++- visuald/LuaD.visualdproj | 1 + 3 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 luad/conversions/pointers.d diff --git a/luad/conversions/pointers.d b/luad/conversions/pointers.d new file mode 100644 index 0000000..104aa26 --- /dev/null +++ b/luad/conversions/pointers.d @@ -0,0 +1,145 @@ +/** +Internal module for pushing and getting pointers. +Pointers are stored in metadata with metatables to enfirce type-safety. +A 'deref' property is created to access the pointer's value. +*/ +module luad.conversions.pointers; + +import luad.conversions.helpers; +import luad.conversions.functions; + +import luad.c.all; +import luad.stack; + +import core.memory; + +import std.traits; +import std.conv; + +void pushGetter(T)(lua_State* L) +{ + static if(isUserStruct!(PointerTarget!T)) + { + static ref PointerTarget!T deref(T ptr) + { + return *ptr; + } + } + else + { + static PointerTarget!T deref(T ptr) + { + return *ptr; + } + } + + lua_pushlightuserdata(L, &deref); + lua_pushcclosure(L, &functionWrapper!(typeof(&deref)), 1); +} + +private void pushGetters(T)(lua_State* L) +{ + lua_newtable(L); // -2 is getters + lua_newtable(L); // -1 is methods + + pushGetter!T(L); + lua_setfield(L, -3, "deref"); + + lua_pushcclosure(L, &index, 2); +} + +void pushSetter(T)(lua_State* L) +{ + static if(isUserStruct!(PointerTarget!T)) + { + static void deref(T ptr, ref PointerTarget!T val) + { + *ptr = val; + } + } + else + { + static void deref(T ptr, PointerTarget!T val) + { + *ptr = val; + } + } + + lua_pushlightuserdata(L, &deref); + lua_pushcclosure(L, &functionWrapper!(typeof(&deref)), 1); +} + +private void pushSetters(T)(lua_State* L) +{ + lua_newtable(L); + + pushSetter!T(L); + lua_setfield(L, -2, "deref"); + + lua_pushcclosure(L, &newIndex, 1); +} + +private void pushMeta(T)(lua_State* L) +{ + 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"); + + static if(!is(Unqual!(PointerTarget!T) == void)) + { + pushGetters!T(L); + lua_setfield(L, -2, "__index"); + static if(isMutable!(PointerTarget!T)) + { + pushSetters!T(L); + lua_setfield(L, -2, "__newindex"); + } + } + + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__metatable"); +} + +void pushPointer(T)(lua_State* L, T value) if (isPointer!T) +{ + T* udata = cast(T*)lua_newuserdata(L, T.sizeof); + *udata = value; + + GC.addRoot(udata); + + pushMeta!T(L); + lua_setmetatable(L, -2); +} + + +T getPointer(T)(lua_State* L, int idx) if(isPointer!T) +{ + verifyType!T(L, idx); + + T* udata = cast(T*)lua_touserdata(L, idx); + return *udata; +} + +version(unittest) +{ + import luad.base; +} + +unittest +{ + import luad.testing; + + lua_State* L = luaL_newstate(); + scope(success) lua_close(L); + luaL_openlibs(L); + +} diff --git a/luad/stack.d b/luad/stack.d index d8ff371..28e0fbf 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.enums; +import luad.conversions.pointers; import luad.conversions.variant; import luad.conversions.helpers; @@ -136,6 +137,12 @@ void pushValue(T)(lua_State* L, T value) if(!isUserStruct!T) else static if(isSomeFunction!T) pushFunction(L, value); + else static if(isPointer!T) + { + // TODO: if value == null -> lua_pushnil(L); + pushPointer(L, value); + } + else static if(is(T == class)) { if(value is null) @@ -195,7 +202,7 @@ template luaTypeOf(T) else static if(isArray!T || isAssociativeArray!T || is(T == LuaTable)) enum luaTypeOf = LUA_TTABLE; - else static if(is(T : const(Object)) || is(T == struct)) + else static if(is(T : const(Object)) || is(T == struct) || isPointer!T) enum luaTypeOf = LUA_TUSERDATA; else @@ -243,7 +250,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 : const(Object))) + static if(is(T : const(Object)) || isPointer!T) { if(type == LuaType.Nil) return null; @@ -315,6 +322,9 @@ T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int else static if(isSomeFunction!T) return getFunction!T(L, idx); + else static if(isPointer!T) + return getPointer!T(L, idx); + else static if(is(T : const(Object))) return getClassInstance!T(L, idx); diff --git a/visuald/LuaD.visualdproj b/visuald/LuaD.visualdproj index b4ee7b0..4fb740a 100644 --- a/visuald/LuaD.visualdproj +++ b/visuald/LuaD.visualdproj @@ -299,6 +299,7 @@ + From ee7ab6d6173944854e17a66c497168ca8e7a2c6f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 19 Aug 2014 02:45:50 +1000 Subject: [PATCH 4/8] 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) { From 7ededc8d55511875fbce619d1dc8792b6f74b576 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 10 Sep 2014 01:45:24 +1000 Subject: [PATCH 5/8] popValue!(const(T)) can now receive values of T, const(T) and immutable(T). Added preliminary for class inheritance. --- luad/conversions/classes.d | 11 ++++++-- luad/conversions/helpers.d | 57 ++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/luad/conversions/classes.d b/luad/conversions/classes.d index e058e5b..bd85d0e 100644 --- a/luad/conversions/classes.d +++ b/luad/conversions/classes.d @@ -109,6 +109,14 @@ private void pushMeta(T)(lua_State* L) // 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"); } @@ -129,8 +137,7 @@ T getClassInstance(T)(lua_State* L, int idx) if (is(T == class)) //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; + return *cast(T*)lua_touserdata(L, idx); } template hasCtor(T) diff --git a/luad/conversions/helpers.d b/luad/conversions/helpers.d index d2b8f0b..dbc6e08 100644 --- a/luad/conversions/helpers.d +++ b/luad/conversions/helpers.d @@ -60,12 +60,23 @@ template SetterTypes(T, string member) alias SetterTypes = TypeTuple!(typeof(mixin("T."~member))); } +// produce a tuple of aliases to overloads which aren't properties 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)); } +// produce a tuple of types that may be assigned to T +template TypeCandidates(T) +{ + // TODO: this doesn't work when types have an indirection (ie, const(int)[]) + static if(is(T == const(U), U)) + alias TypeCandidates = TypeTuple!(U, T, immutable(U)); + else + alias TypeCandidates = T; +} + struct Ref(T) { alias __instance this; @@ -308,25 +319,49 @@ template isStaticMember(T, string member) enum isStaticMember = false; } +template mangledTypeCandidates(T) +{ + template impl(Ty...) + { + static if(Ty.length == 0) + alias impl = TypeTuple!(); + else + alias impl = TypeTuple!(Ty[0].mangleof, impl!(Ty[1..$])); + } + alias mangledTypeCandidates = impl!(TypeCandidates!T); +} + 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 + int popNum = 0; + do + { + lua_getfield(L, -1, "__dmangle"); //must be a D object - // TODO: support pointers... + popNum += 2; // metatable and metatable.__dmangle - // 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)); + // TODO: support pointers... + + size_t manglelen; + auto cmangle = lua_tolstring(L, -1, &manglelen); + + foreach(ty; mangledTypeCandidates!T) + { + if(cmangle[0 .. manglelen] == ty) + { + lua_pop(L, popNum); + return; + } + } } - lua_pop(L, 2); //metatable and metatable.__dmangle + while(is(T == class) && lua_getmetatable(L, -2) != 0); + + lua_getfield(L, -popNum, "__dtype"); + auto cname = lua_tostring(L, -1); + luaL_error(L, `attempt to get instance %s as type "%s"`, cname, T.stringof.ptr); } From f395d01c92f6f3e864780486fdaa0f9d9ded6526 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 19 Aug 2014 00:25:33 +1000 Subject: [PATCH 6/8] local cruft --- luad/conversions/enums.d | 35 +++++++++++++++++++++++++++++++++++ luad/state.d | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/luad/conversions/enums.d b/luad/conversions/enums.d index 974861f..5cd65de 100644 --- a/luad/conversions/enums.d +++ b/luad/conversions/enums.d @@ -78,6 +78,41 @@ string getEnumFromValue(Enum)(Enum value) return null; } +/+ TODO: I'd quite like to support bitfields too... +uint getBitfieldValue(Enum)(const(char)[] flags) +{ + uint value; + foreach(token; flags.splitter('|').map!(a => a.strip).filter!(a => !a.empty)) + { + Enum val = getEnumValue!Enum(token); + if(val != cast(Enum)-1) + value |= val; + } + return value; +} + +string getBitfieldFromValue(Enum)(uint bits) +{ + string bitfield; + foreach(i; 0..32) + { + uint bit = 1 << i; + if(!(bits & bit)) + continue; + + string key = getEnumFromValue(cast(Enum)bit); + if(key) + { + if(!bitfield) + bitfield = key; + else + bitfield = bitfield ~ "|" ~ key; + } + } + return bitfield; +} ++/ + void pushEnum(T)(lua_State* L, T value) if (is(T == enum)) { string key = getEnumFromValue(value); diff --git a/luad/state.d b/luad/state.d index 6c350bc..cf89e64 100644 --- a/luad/state.d +++ b/luad/state.d @@ -99,7 +99,7 @@ public: } /// The underlying $(D lua_State) pointer for interfacing with C. - @property lua_State* state() nothrow pure @safe + @property inout(lua_State)* state() inout nothrow pure @safe { return L; } From 488994e617e390081355a58fd10afdddb7bf32b3 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 11 Jan 2015 16:16:01 +1000 Subject: [PATCH 7/8] Fixed for DMD2.066.1 --- luad/conversions/helpers.d | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/luad/conversions/helpers.d b/luad/conversions/helpers.d index dbc6e08..023b82b 100644 --- a/luad/conversions/helpers.d +++ b/luad/conversions/helpers.d @@ -204,7 +204,7 @@ void pushGetter(T, string member)(lua_State* L) { ref RT get() { - T _this = *cast(T*)&this; + T _this = cast(T)cast(void*)this; return mixin("_this."~member); } } @@ -212,7 +212,7 @@ void pushGetter(T, string member)(lua_State* L) { RT get() { - T _this = *cast(T*)&this; + T _this = cast(T)cast(void*)this; return mixin("_this."~member); } } @@ -263,7 +263,7 @@ void pushSetter(T, string member)(lua_State* L) { final void set(ref ArgType value) { - T _this = *cast(T*)&this; + T _this = cast(T)cast(void*)this; mixin("_this."~member) = value; } } @@ -271,7 +271,7 @@ void pushSetter(T, string member)(lua_State* L) { final void set(ArgType value) { - T _this = *cast(T*)&this; + T _this = cast(T)cast(void*)this; mixin("_this."~member) = value; } } From f4acf0264b3e9c9b87825ecb0b36bf262f957a92 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 10 May 2015 15:52:52 +1000 Subject: [PATCH 8/8] Minor fixes. --- luad/conversions/enums.d | 1 + luad/state.d | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/luad/conversions/enums.d b/luad/conversions/enums.d index 5cd65de..10e7db8 100644 --- a/luad/conversions/enums.d +++ b/luad/conversions/enums.d @@ -15,6 +15,7 @@ module luad.conversions.enums; import luad.c.all; import luad.stack; +import std.typetuple; import std.traits; import std.conv; import std.range; diff --git a/luad/state.d b/luad/state.d index cf89e64..54a496f 100644 --- a/luad/state.d +++ b/luad/state.d @@ -40,9 +40,9 @@ public: * * See_Also: $(MREF LuaState.openLibs) */ - this() + this(lua_Alloc alloc = null, void* userdata = null) { - lua_State* L = luaL_newstate(); + lua_State* L = alloc ? lua_newstate(alloc, userdata) : luaL_newstate(); owner = true; extern(C) static int panic(lua_State* L)