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 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;
diff --git a/visuald/LuaD.visualdproj b/visuald/LuaD.visualdproj
index 8218571..ca1f2d2 100644
--- a/visuald/LuaD.visualdproj
+++ b/visuald/LuaD.visualdproj
@@ -297,6 +297,7 @@
+