1 module luad.dynamic;
2 
3 import luad.c.all;
4 
5 import luad.base;
6 import luad.stack;
7 
8 /**
9  * Represents a reference to a Lua value of any type.
10  * Supports all operations you can perform on values in Lua.
11  */
12 struct LuaDynamic
13 {
14 	/**
15 	 * Underlying Lua reference.
16 	 * LuaDynamic does not sub-type LuaObject - qualify access to this reference explicitly.
17 	 */
18 	LuaObject object;
19 
20 	/**
21 	 * Perform a Lua method call on this object.
22 	 *
23 	 * Performs a call similar to calling functions in Lua with the colon operator.
24 	 * The name string is looked up in this object and the result is called. This object is prepended
25 	 * to the arguments args.
26 	 * Params:
27 	 *    name = _name of method
28 	 *    args = additional arguments
29 	 * Returns:
30 	 *    All return values
31 	 * Examples:
32 	 * ----------------
33 	 * auto luaString = lua.wrap!LuaDynamic("test");
34 	 * auto results = luaString.gsub("t", "f"); // opDispatch
35 	 * assert(results[0] == "fesf");
36 	 * assert(results[1] == 2); // two instances of 't' replaced
37 	 * ----------------
38 	 * Note:
39 	 *    To call a member named "object", instantiate this function template explicitly.
40 	 */
41 	LuaDynamic[] opDispatch(string name, string file = __FILE__, uint line = __LINE__, Args...)(Args args)
42 	{
43 		// Push self
44 		object.push();
45 
46 		auto frame = lua_gettop(object.state);
47 
48 		// push name and self[name]
49 		lua_pushlstring(object.state, name.ptr, name.length);
50 		lua_gettable(object.state, -2);
51 
52 		// TODO: How do I properly generalize this to include other types,
53 		// while not stepping on the __call metamethod?
54 		if(lua_isnil(object.state, -1))
55 		{
56 			lua_pop(object.state, 2);
57 			luaL_error(object.state, "%s:%d: attempt to call method '%s' (a nil value)", file.ptr, line, name.ptr);
58 		}
59 
60 		// Copy 'this' to the top of the stack
61 		lua_pushvalue(object.state, -2);
62 
63 		foreach(arg; args)
64 			pushValue(object.state, arg);
65 
66 		lua_call(object.state, args.length + 1, LUA_MULTRET);
67 
68 		auto nret = lua_gettop(object.state) - frame;
69 
70 		auto ret = popStack!LuaDynamic(object.state, nret);
71 
72 		// Pop self
73 		lua_pop(object.state, 1);
74 
75 		return ret;
76 	}
77 
78 	/**
79 	 * Call this object.
80 	 * This object must either be a function, or have a metatable providing the ___call metamethod.
81 	 * Params:
82 	 *    args = arguments for the call
83 	 * Returns:
84 	 *    Array of return values, or a null array if there were no return values
85 	 */
86 	LuaDynamic[] opCall(Args...)(Args args)
87 	{
88 		auto frame = lua_gettop(object.state);
89 
90 		object.push(); // Callable
91 		foreach(arg; args)
92 			pushValue(object.state, arg);
93 
94 		lua_call(object.state, args.length, LUA_MULTRET);
95 
96 		auto nret = lua_gettop(object.state) - frame;
97 
98 		return popStack!LuaDynamic(object.state, nret);
99 	}
100 
101 	/**
102 	 * Index this object.
103 	 * This object must either be a table, or have a metatable providing the ___index metamethod.
104 	 * Params:
105 	 *    key = _key to lookup
106 	 */
107 	LuaDynamic opIndex(T)(auto ref T key)
108 	{
109 		object.push();
110 		pushValue(object.state, key);
111 		lua_gettable(object.state, -2);
112 		auto result = getValue!LuaDynamic(object.state, -1);
113 		lua_pop(object.state, 2);
114 		return result;
115 	}
116 
117 	/**
118 	 * Compare the referenced object to another value with Lua's equality semantics.
119 	 * If the _other value is not a Lua reference wrapper, it will go through the
120 	 * regular D to Lua conversion process first.
121 	 * To check for nil, compare against the special constant "nil".
122 	 */
123 	bool opEquals(T)(auto ref T other)
124 	{
125 		object.push();
126 		static if(is(T == Nil))
127 		{
128 			scope(success) lua_pop(object.state, 1);
129 			return lua_isnil(object.state, -1) == 1;
130 		}
131 		else
132 		{
133 			pushValue(object.state, other);
134 			scope(success) lua_pop(object.state, 2);
135 			return lua_equal(object.state, -1, -2);
136 		}
137 	}
138 }
139 
140 version(unittest) import luad.testing;
141 
142 import std.stdio;
143 
144 unittest
145 {
146 	lua_State* L = luaL_newstate();
147 	scope(success) lua_close(L);
148 	luaL_openlibs(L);
149 
150 	luaL_dostring(L, `str = "test"`);
151 	lua_getglobal(L, "str");
152 	auto luaString = popValue!LuaDynamic(L);
153 
154 	LuaDynamic[] results = luaString.gsub("t", "f");
155 
156 	assert(results[0] == "fesf");
157 	assert(results[1] == 2); // two instances of 't' replaced
158 
159 	auto gsub = luaString["gsub"];
160 	assert(gsub.object.type == LuaType.Function);
161 
162 	LuaDynamic[] results2 = gsub(luaString, "t", "f");
163 	assert(results[0] == results2[0]);
164 	assert(results[1] == results2[1]);
165 	assert(results == results2);
166 
167 	lua_getglobal(L, "thisisnil");
168 	auto nilRef = popValue!LuaDynamic(L);
169 
170 	assert(nilRef == nil);
171 }