From f1279aa7fbd0044b1872966c0c32daa2b6fddc66 Mon Sep 17 00:00:00 2001 From: IvanC Date: Sat, 16 Aug 2014 11:34:01 +0100 Subject: [PATCH 1/6] Add support for user defined attributes / overloads Modifies class registration, adding support for the usage of UDAs that define alternate method names. Also adds a NoScript attribute that prevents the method from being registered. --- luad/all.d | 2 +- luad/conversions/classes.d | 44 +++++++++++++-- luad/conversions/functions.d | 100 ++++++++++++++++++++++++++++++++++- 3 files changed, 141 insertions(+), 5 deletions(-) diff --git a/luad/all.d b/luad/all.d index a92c9ae..04ce693 100644 --- a/luad/all.d +++ b/luad/all.d @@ -7,4 +7,4 @@ module luad.all; public import luad.base, luad.table, luad.lfunction, luad.dynamic, luad.state, luad.lmodule; -public import luad.conversions.functions : LuaVariableReturn, variableReturn; +public import luad.conversions.functions : LuaVariableReturn, variableReturn, NoScript, ScriptRename, ScriptAffix; diff --git a/luad/conversions/classes.d b/luad/conversions/classes.d index cc9d78a..2f01c58 100644 --- a/luad/conversions/classes.d +++ b/luad/conversions/classes.d @@ -43,10 +43,19 @@ private void pushMeta(T)(lua_State* L, T obj) member != "toString" && member != "opEquals" && //handle below member != "opCmp") //handle below { - static if(__traits(getOverloads, T.init, member).length > 0 && !__traits(isStaticFunction, mixin("T." ~ member))) + enum overloadCount = __traits(getOverloads, T.init, member).length; + + static if(!__traits(isStaticFunction, mixin("T." ~ member))) { - pushMethod!(T, member)(L); - lua_setfield(L, -2, toStringz(member)); + static if(overloadCount == 1) + { + string methodName = pushMethod!(T, member)(L); + + if(methodName != null) + lua_setfield(L, -2, toStringz(methodName)); + } + else static if(overloadCount > 1) + pushOverloadedMethod!(T, member)(L); } } } @@ -286,3 +295,32 @@ unittest lua_setglobal(L, "checkNull"); unittest_lua(L, `checkNull(nil)`); } + +// User defined attribute test +unittest +{ + static class A + { + int n = 5; + + void foo(int x) @property @ScriptAffix("set") + { + n = x; + } + + int foo() @property @ScriptRename("givefoo") { return n; } + + void bar() @NoScript { } + } + + L = luaL_newstate(); + + A a = new A(); + pushValue(L, a); + lua_setglobal(L, "A"); + + unittest_lua(L, "assert(A != nil)"); + unittest_lua(L, "assert(A:givefoo() == 5)"); + unittest_lua(L, "A:setfoo(10); assert(A:givefoo() == 10)"); + unittest_lua(L, "assert(A.bar == nil)"); +} \ No newline at end of file diff --git a/luad/conversions/functions.d b/luad/conversions/functions.d index 1ff32f2..3a21693 100644 --- a/luad/conversions/functions.d +++ b/luad/conversions/functions.d @@ -24,6 +24,22 @@ import luad.c.all; import luad.stack; +// User defined attributes that can be placed on functions in order to change the binding behavior +// Prevents this function from being registered +enum NoScript = "NoScript"; + +// Changes the name used to register this function +struct ScriptRename +{ + string NewName; +} + +struct ScriptAffix +{ + string Affix; + bool IsSuffix = false; +} + private void argsError(lua_State* L, int nargs, ptrdiff_t expected) { lua_Debug debugInfo; @@ -248,10 +264,32 @@ void pushFunction(T)(lua_State* L, T func) if (isSomeFunction!T) lua_pushcclosure(L, &functionWrapper!T, 1); } +// Utility template that creates an iterable range +template StaticRange(int count) +{ + static if (count == 0) + alias TypeTuple!() StaticRange; + else + alias TypeTuple!(StaticRange!(count - 1), count - 1) StaticRange; +} + // TODO: optimize for non-virtual functions -void pushMethod(Class, string member)(lua_State* L) if (isSomeFunction!(__traits(getMember, Class, member))) +string pushMethod(Class, string member)(lua_State* L) if (isSomeFunction!(__traits(getMember, Class, member))) { alias typeof(mixin("&Class.init." ~ member)) T; + enum attributes = __traits(getAttributes, __traits(getMember, Class, member)); + string funcName = member; + + foreach (attrib; attributes) + { + static if(is(typeof(attrib) == string) && (attrib == NoScript)) + return null; + + static if(is(typeof(attrib) == ScriptRename)) + funcName = attrib.NewName; + else static if(is(typeof(attrib) == ScriptAffix)) + funcName = attrib.IsSuffix ? (funcName ~ attrib.Affix) : (attrib.Affix ~ funcName); + } // Delay vtable lookup until the right time static ReturnType!T virtualWrapper(Class self, ParameterTypeTuple!T args) @@ -261,6 +299,66 @@ void pushMethod(Class, string member)(lua_State* L) if (isSomeFunction!(__traits lua_pushlightuserdata(L, &virtualWrapper); lua_pushcclosure(L, &methodWrapper!(T, Class, true), 1); + return funcName; +} + +// Called when binding a method that has multiple overloads, also used for properties for obvious reasons +void pushOverloadedMethod(Class, string member)(lua_State* L) if (isSomeFunction!(__traits(getMember, Class, member))) +{ + enum overloadCount = __traits(getOverloads, Class, member).length; + alias TypeTuple!(__traits(getOverloads, Class, member)) Overloads; + + foreach(overloadidx; StaticRange!overloadCount) + { + alias Overloads[overloadidx] T; + + // Find out if any known UDAs are defined for this overload and apply them + enum attributes = __traits(getAttributes, T); + bool noScript = false; + string funcName = member; + + foreach (attrib; attributes) + { + static if(is(typeof(attrib) == string) && (attrib == NoScript)) + { + noScript = true; + break; + } + + static if(is(typeof(attrib) == ScriptRename)) + { + funcName = attrib.NewName; + + // Delay vtable lookup until the right time + mixin(" + static ReturnType!T vw" ~ attrib.NewName ~ "(Class self, ParameterTypeTuple!T args) + { + return mixin(\"self.\" ~ member)(args); + } + lua_pushlightuserdata(L, &vw" ~ attrib.NewName ~ "); + "); + } + else static if(is(typeof(attrib) == ScriptAffix)) + { + funcName = attrib.IsSuffix ? (funcName ~ attrib.Affix) : (attrib.Affix ~ funcName); + + // Delay vtable lookup until the right time + mixin(" + static ReturnType!T vw" ~ attrib.Affix ~ "(Class self, ParameterTypeTuple!T args) + { + return mixin(\"self.\" ~ member)(args); + } + lua_pushlightuserdata(L, &vw" ~ attrib.Affix ~ "); + "); + } + } + + if(noScript) + continue; + + lua_pushcclosure(L, &methodWrapper!(FunctionTypeOf!T, Class, true), 1); + lua_setfield(L, -2, toStringz(funcName)); + } } /** From e020c0995323477551d1eb059d232bb7a6b0b624 Mon Sep 17 00:00:00 2001 From: IvanC Date: Sat, 16 Aug 2014 12:07:10 +0100 Subject: [PATCH 2/6] Changes UDA order in tests DMD Issue 11678 --- luad/conversions/classes.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/luad/conversions/classes.d b/luad/conversions/classes.d index 2f01c58..3192fac 100644 --- a/luad/conversions/classes.d +++ b/luad/conversions/classes.d @@ -303,12 +303,12 @@ unittest { int n = 5; - void foo(int x) @property @ScriptAffix("set") + @ScriptAffix("set") @property void foo(int x) { n = x; } - int foo() @property @ScriptRename("givefoo") { return n; } + @ScriptRename("givefoo") @property int foo() { return n; } void bar() @NoScript { } } From bb5836044997dc2de505a7a4307c6f0b0d7ffec5 Mon Sep 17 00:00:00 2001 From: IvanC Date: Sat, 16 Aug 2014 12:14:06 +0100 Subject: [PATCH 3/6] More UDA order fixes --- luad/conversions/classes.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/luad/conversions/classes.d b/luad/conversions/classes.d index 3192fac..3720566 100644 --- a/luad/conversions/classes.d +++ b/luad/conversions/classes.d @@ -310,7 +310,7 @@ unittest @ScriptRename("givefoo") @property int foo() { return n; } - void bar() @NoScript { } + @NoScript void bar() { } } L = luaL_newstate(); From 666e731a1210c5a29f5940e3c7142fc6163b64e0 Mon Sep 17 00:00:00 2001 From: IvanC Date: Mon, 18 Aug 2014 02:26:57 +0100 Subject: [PATCH 4/6] Fixed unit tests --- luad/conversions/classes.d | 3 ++- luad/conversions/functions.d | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/luad/conversions/classes.d b/luad/conversions/classes.d index 3720566..0da6305 100644 --- a/luad/conversions/classes.d +++ b/luad/conversions/classes.d @@ -314,12 +314,13 @@ unittest } L = luaL_newstate(); + luaL_openlibs(L); A a = new A(); pushValue(L, a); lua_setglobal(L, "A"); - unittest_lua(L, "assert(A != nil)"); + unittest_lua(L, "assert(A ~= nil)"); unittest_lua(L, "assert(A:givefoo() == 5)"); unittest_lua(L, "A:setfoo(10); assert(A:givefoo() == 10)"); unittest_lua(L, "assert(A.bar == nil)"); diff --git a/luad/conversions/functions.d b/luad/conversions/functions.d index 3a21693..e161155 100644 --- a/luad/conversions/functions.d +++ b/luad/conversions/functions.d @@ -674,7 +674,7 @@ unittest //C varargs require at least one fixed argument. static string concat_cvar (int count, ...) { - import core.stdc.stdarg; + import core.vararg; string result; va_list args; From ae7016afdc86049f9cb26bb6b62c22db246a0216 Mon Sep 17 00:00:00 2001 From: IvanC Date: Mon, 18 Aug 2014 02:32:14 +0100 Subject: [PATCH 5/6] Ditto --- luad/conversions/functions.d | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/luad/conversions/functions.d b/luad/conversions/functions.d index e161155..5e8ff33 100644 --- a/luad/conversions/functions.d +++ b/luad/conversions/functions.d @@ -626,6 +626,8 @@ unittest // Variadic function arguments unittest { + import core.vararg; + static string concat(const(char)[][] pieces...) { string result; @@ -674,7 +676,6 @@ unittest //C varargs require at least one fixed argument. static string concat_cvar (int count, ...) { - import core.vararg; string result; va_list args; @@ -694,8 +695,8 @@ unittest //D-style variadics have an _arguments array that specifies //the type of each passed argument. - static string concat_dvar (...) { - import core.vararg; + static string concat_dvar (...) + { string result; foreach (argtype; _arguments) { From 9763cd614a54ccb7830a0497f88145890a67f507 Mon Sep 17 00:00:00 2001 From: IvanC Date: Mon, 18 Aug 2014 14:59:12 +0100 Subject: [PATCH 6/6] Incorporated feedback from PR - Shortened script UDAs and made them lowercase. - Split ScriptAffix into prefix and suffix. - Moved UDA logic to separate functions. - Cleaned up pushOverloadedMethod. --- luad/all.d | 2 +- luad/conversions/classes.d | 12 ++- luad/conversions/functions.d | 169 ++++++++++++++++------------------- 3 files changed, 87 insertions(+), 96 deletions(-) diff --git a/luad/all.d b/luad/all.d index 04ce693..b1d5ee2 100644 --- a/luad/all.d +++ b/luad/all.d @@ -7,4 +7,4 @@ module luad.all; public import luad.base, luad.table, luad.lfunction, luad.dynamic, luad.state, luad.lmodule; -public import luad.conversions.functions : LuaVariableReturn, variableReturn, NoScript, ScriptRename, ScriptAffix; +public import luad.conversions.functions : LuaVariableReturn, variableReturn, noscript, rename, prefix, suffix; diff --git a/luad/conversions/classes.d b/luad/conversions/classes.d index 0da6305..25ca335 100644 --- a/luad/conversions/classes.d +++ b/luad/conversions/classes.d @@ -303,14 +303,15 @@ unittest { int n = 5; - @ScriptAffix("set") @property void foo(int x) + @prefix("set") @property void foo(int x) { n = x; } - @ScriptRename("givefoo") @property int foo() { return n; } + @rename("givefoo") @property int foo() { return n; } - @NoScript void bar() { } + @noscript() @rename("shouldntExist") void bar() { } + @rename("shouldExist") void newbar() { } } L = luaL_newstate(); @@ -321,7 +322,10 @@ unittest lua_setglobal(L, "A"); unittest_lua(L, "assert(A ~= nil)"); + unittest_lua(L, "assert(A.shouldntExist == nil)"); + unittest_lua(L, "assert(A.bar == nil)"); + unittest_lua(L, "assert(A.shouldExist ~= nil)"); + unittest_lua(L, "assert(A.newbar == nil)"); unittest_lua(L, "assert(A:givefoo() == 5)"); unittest_lua(L, "A:setfoo(10); assert(A:givefoo() == 10)"); - unittest_lua(L, "assert(A.bar == nil)"); } \ No newline at end of file diff --git a/luad/conversions/functions.d b/luad/conversions/functions.d index 5e8ff33..9b528ad 100644 --- a/luad/conversions/functions.d +++ b/luad/conversions/functions.d @@ -24,22 +24,6 @@ import luad.c.all; import luad.stack; -// User defined attributes that can be placed on functions in order to change the binding behavior -// Prevents this function from being registered -enum NoScript = "NoScript"; - -// Changes the name used to register this function -struct ScriptRename -{ - string NewName; -} - -struct ScriptAffix -{ - string Affix; - bool IsSuffix = false; -} - private void argsError(lua_State* L, int nargs, ptrdiff_t expected) { lua_Debug debugInfo; @@ -147,6 +131,54 @@ int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) return pushReturnValues(L, call()); } +// User defined attributes that can be placed on functions in order to change the binding behavior +// Prevents this function from being registered +struct noscript {} + +// Changes the name used to register this function +struct rename +{ + string Name; +} + +struct prefix +{ + string Affix; +} + +struct suffix +{ + string Affix; +} + +private: + +bool isNoScript(Attributes...)() +{ + foreach (attribute; Attributes) + { + static if(is(typeof(attribute) == noscript)) + return true; + } + + return false; +} + +string getProperName(string member, Attributes...)() +{ + foreach (attribute; Attributes) + { + static if(is(typeof(attribute) == rename)) + return attribute.Name; + else static if(is(typeof(attribute) == prefix)) + return attribute.Affix ~ member; + else static if(is(typeof(attribute) == suffix)) + return member ~ attribute.Affix; + } + + return member; +} + private: // TODO: right now, virtual functions on specialized classes can be called with base classes as 'self', not safe! @@ -264,100 +296,55 @@ void pushFunction(T)(lua_State* L, T func) if (isSomeFunction!T) lua_pushcclosure(L, &functionWrapper!T, 1); } -// Utility template that creates an iterable range -template StaticRange(int count) -{ - static if (count == 0) - alias TypeTuple!() StaticRange; - else - alias TypeTuple!(StaticRange!(count - 1), count - 1) StaticRange; -} - -// TODO: optimize for non-virtual functions string pushMethod(Class, string member)(lua_State* L) if (isSomeFunction!(__traits(getMember, Class, member))) { alias typeof(mixin("&Class.init." ~ member)) T; - enum attributes = __traits(getAttributes, __traits(getMember, Class, member)); - string funcName = member; + alias attributes = TypeTuple!(__traits(getAttributes, __traits(getMember, Class, member))); - foreach (attrib; attributes) + static if ((attributes.length == 0) || !isNoScript!attributes) { - static if(is(typeof(attrib) == string) && (attrib == NoScript)) - return null; + // Delay vtable lookup until the right time + static ReturnType!T virtualWrapper(Class self, ParameterTypeTuple!T args) + { + return mixin("self." ~ member)(args); + } - static if(is(typeof(attrib) == ScriptRename)) - funcName = attrib.NewName; - else static if(is(typeof(attrib) == ScriptAffix)) - funcName = attrib.IsSuffix ? (funcName ~ attrib.Affix) : (attrib.Affix ~ funcName); - } + lua_pushlightuserdata(L, &virtualWrapper); + lua_pushcclosure(L, &methodWrapper!(T, Class, true), 1); - // Delay vtable lookup until the right time - static ReturnType!T virtualWrapper(Class self, ParameterTypeTuple!T args) - { - return mixin("self." ~ member)(args); + static if(attributes.length > 0) + return getProperName!(member, attributes); + else + return member; } - - lua_pushlightuserdata(L, &virtualWrapper); - lua_pushcclosure(L, &methodWrapper!(T, Class, true), 1); - return funcName; + else + return null; } // Called when binding a method that has multiple overloads, also used for properties for obvious reasons void pushOverloadedMethod(Class, string member)(lua_State* L) if (isSomeFunction!(__traits(getMember, Class, member))) { - enum overloadCount = __traits(getOverloads, Class, member).length; - alias TypeTuple!(__traits(getOverloads, Class, member)) Overloads; + alias Overloads = TypeTuple!(__traits(getOverloads, Class, member)); - foreach(overloadidx; StaticRange!overloadCount) + template virtualWrapper(alias Args, FuncT) { - alias Overloads[overloadidx] T; - - // Find out if any known UDAs are defined for this overload and apply them - enum attributes = __traits(getAttributes, T); - bool noScript = false; - string funcName = member; - - foreach (attrib; attributes) + static ReturnType!FuncT vwFunc(Class self, ParameterTypeTuple!Args args) { - static if(is(typeof(attrib) == string) && (attrib == NoScript)) - { - noScript = true; - break; - } - - static if(is(typeof(attrib) == ScriptRename)) - { - funcName = attrib.NewName; - - // Delay vtable lookup until the right time - mixin(" - static ReturnType!T vw" ~ attrib.NewName ~ "(Class self, ParameterTypeTuple!T args) - { - return mixin(\"self.\" ~ member)(args); - } - lua_pushlightuserdata(L, &vw" ~ attrib.NewName ~ "); - "); - } - else static if(is(typeof(attrib) == ScriptAffix)) - { - funcName = attrib.IsSuffix ? (funcName ~ attrib.Affix) : (attrib.Affix ~ funcName); - - // Delay vtable lookup until the right time - mixin(" - static ReturnType!T vw" ~ attrib.Affix ~ "(Class self, ParameterTypeTuple!T args) - { - return mixin(\"self.\" ~ member)(args); - } - lua_pushlightuserdata(L, &vw" ~ attrib.Affix ~ "); - "); - } + return mixin("self." ~ member)(args); } + } - if(noScript) - continue; + foreach(overload; Overloads) + { + alias FnType = FunctionTypeOf!overload; + alias attributes = TypeTuple!(__traits(getAttributes, overload)); - lua_pushcclosure(L, &methodWrapper!(FunctionTypeOf!T, Class, true), 1); - lua_setfield(L, -2, toStringz(funcName)); + static if ((attributes.length == 0) || !isNoScript!attributes) + { + lua_pushlightuserdata(L, &virtualWrapper!(overload, FnType).vwFunc); + lua_pushcclosure(L, &methodWrapper!(FnType, Class, true), 1); + lua_setfield(L, -2, toStringz(getProperName!(member, attributes))); + } } }