1 /**
2 Internal module for pushing and getting _structs.
3 
4 A struct is treated as a table layout schema.
5 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.
6 Struct methods are treated as if they were delegate fields pointing to the method.
7 For an example, see the "Configuration File" example on the $(LINK2 $(REFERENCETOP),front page).
8 */
9 module luad.conversions.structs;
10 
11 import luad.c.all;
12 
13 import luad.stack;
14 
15 private template isInternal(string field)
16 {
17 	enum isInternal = field.length >= 2 && field[0..2] == "__";
18 }
19 
20 //TODO: ignore static fields, post-blits, destructors, etc?
21 void pushStruct(T)(lua_State* L, ref T value) if (is(T == struct))
22 {
23 	lua_createtable(L, 0, value.tupleof.length);
24 
25 	foreach(field; __traits(allMembers, T))
26 	{
27 		static if(!isInternal!field &&
28 		          field != "this" &&
29 		          field != "opAssign")
30 		{
31 			pushValue(L, field);
32 
33 			enum isMemberFunction = mixin("is(typeof(&value." ~ field ~ ") == delegate)");
34 
35 			static if(isMemberFunction)
36 				pushValue(L, mixin("&value." ~ field));
37 			else
38 				pushValue(L, mixin("value." ~ field));
39 
40 			lua_settable(L, -3);
41 		}
42 	}
43 }
44 
45 T getStruct(T)(lua_State* L, int idx) if(is(T == struct))
46 {
47 	T s;
48 	fillStruct(L, idx, s);
49 	return s;
50 }
51 
52 void fillStruct(T)(lua_State* L, int idx, ref T s) if(is(T == struct))
53 {
54 	foreach(field; __traits(allMembers, T))
55 	{
56 		static if(field != "this" && !isInternal!(field))
57 		{
58 			static if(__traits(getOverloads, T, field).length == 0)
59 			{
60 				lua_getfield(L, idx, field.ptr);
61 				if(lua_isnil(L, -1) == 0) {
62 					mixin("s." ~ field ~
63 					  " = popValue!(typeof(s." ~ field ~ "))(L);");
64 				} else
65 					lua_pop(L, 1);
66 			}
67 		}
68 	}
69 }
70 
71 version(unittest)
72 {
73 	import luad.base;
74 	struct S
75 	{
76 		LuaObject o;
77 		int i;
78 		double n;
79 		string s;
80 
81 		string f(){ return "foobar"; }
82 	}
83 }
84 
85 unittest
86 {
87 	import luad.testing;
88 
89 	lua_State* L = luaL_newstate();
90 	scope(success) lua_close(L);
91 	luaL_openlibs(L);
92 
93 	pushValue(L, "test");
94 	auto obj = popValue!LuaObject(L);
95 
96 	pushValue(L, S(obj, 1, 2.3, "hello"));
97 	assert(lua_istable(L, -1));
98 	lua_setglobal(L, "struct");
99 
100 	unittest_lua(L, `
101 		for key, expected in pairs{i = 1, n = 2.3, s = "hello"} do
102 			local value = struct[key]
103 			assert(value == expected,
104 				("bad table pair: '%s' = '%s' (expected '%s')"):format(key, value, expected)
105 			)
106 		end
107 
108 		assert(struct.f() == "foobar")
109 	`);
110 
111 	lua_getglobal(L, "struct");
112 	S s = getStruct!S(L, -1);
113 
114 	assert(s.o == obj);
115 	assert(s.i == 1);
116 	assert(s.n == 2.3);
117 	assert(s.s == "hello");
118 
119 	lua_pop(L, 1);
120 
121 	struct S2
122 	{
123 		string a, b;
124 	}
125 
126 	unittest_lua(L, `
127 		incompleteStruct = {a = "foo"}
128 	`);
129 
130 	lua_getglobal(L, "incompleteStruct");
131 	S2 s2 = getStruct!S2(L, -1);
132 
133 	assert(s2.a == "foo");
134 	assert(s2.b == null);
135 
136 	lua_pop(L, 1);
137 }