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 }