1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3
4 #include "LuaMetaType.h"
5 #include "Lua.h"
6 #include "LuaObject.h"
7 #include "PropertyMap.h"
8
9 // if found, returns true, leaves item to return to lua on top of stack
10 // if not found, returns false
get_method(lua_State * l,int metatable,int name)11 static bool get_method(lua_State *l, int metatable, int name)
12 {
13 LUA_DEBUG_START(l);
14
15 name = lua_absindex(l, name);
16
17 // look up the name in the list of methods.
18 lua_getfield(l, metatable, "methods");
19 lua_pushvalue(l, name); // make a copy of the name
20 lua_rawget(l, -2); // look it up in the methods table
21 lua_remove(l, -2); // remove the methods table
22
23 // found something, return it
24 if (!lua_isnil(l, -1)) {
25 LUA_DEBUG_END(l, 1);
26 return true;
27 }
28 lua_pop(l, 1);
29
30 // otherwise, just return
31 LUA_DEBUG_END(l, 0);
32 return false;
33 }
34
35 // if found, returns true, leaves attribute entry on top of stack
36 // if not found, returns false
get_attr_entry(lua_State * l,int metatable,int name)37 static bool get_attr_entry(lua_State *l, int metatable, int name)
38 {
39 LUA_DEBUG_START(l);
40
41 name = lua_absindex(l, name);
42
43 // lookup the name in the list of attributes
44 lua_getfield(l, metatable, "attrs");
45 lua_pushvalue(l, name); // make a copy of the name
46 lua_rawget(l, -2); // look it up in the methods table
47 lua_remove(l, -2); // remove the attributes table
48
49 // found an attribute entry, don't evaluate
50 if (!lua_isnil(l, -1)) {
51 LUA_DEBUG_END(l, 1);
52 return true;
53 }
54 lua_pop(l, 1);
55
56 // not found
57 LUA_DEBUG_END(l, 0);
58 return false;
59 }
60
61 // takes the metatable, name on top of stack
62 // Look up a method or attribute, evaluating the attribute entry as a getter.
get_method_or_attr(lua_State * l)63 static bool get_method_or_attr(lua_State *l)
64 {
65 LUA_DEBUG_START(l);
66
67 // if we have a method, call it
68 if (get_method(l, -2, -1)) {
69 LUA_DEBUG_END(l, 1);
70 return true;
71 } else if (get_attr_entry(l, -2, -1)) {
72 // Someone may have put a non-function value in the attributes
73 // weird, but ok, we can handle it
74 if (lua_isfunction(l, -1)) {
75 lua_pushvalue(l, 1); // push the self object
76 pi_lua_protected_call(l, 1, 1); // call the attribute entry
77
78 LUA_DEBUG_END(l, 1);
79 return true;
80 }
81
82 LUA_DEBUG_END(l, 1);
83 return true;
84 }
85
86 LUA_DEBUG_END(l, 0);
87 return false;
88 }
89
l_index(lua_State * l)90 static int l_index(lua_State *l)
91 {
92 // userdata are typed, tables are not
93 bool typeless = lua_istable(l, 1);
94 assert(typeless || lua_isuserdata(l, 1));
95
96 // typeless objects have no parents, and have only one method table, so this is easy
97 if (typeless) {
98 lua_getmetatable(l, 1);
99 lua_pushvalue(l, 2);
100
101 // if we have something in the first metatable, return it
102 if (get_method_or_attr(l))
103 return 1;
104
105 // otherwise, nothing further to look up
106 return 0;
107 }
108
109 // normal userdata object
110 // first check properties. we don't need to drill through lua if the
111 // property is already available
112 lua_getuservalue(l, 1);
113 if (!lua_isnil(l, -1)) {
114 lua_pushvalue(l, 2); // push the key
115 lua_gettable(l, -2); // get the property from the table
116 if (!lua_isnil(l, -1)) {
117 return 1; // return the property if it is set
118 }
119
120 lua_pop(l, 1);
121 }
122 lua_pop(l, 1);
123
124 // push the metatype registry here for later
125 lua_getfield(l, LUA_REGISTRYINDEX, "LuaMetaTypes");
126 int metaTypeRegistry = lua_gettop(l);
127
128 // push metatable, name onto the top of the stack
129 lua_getmetatable(l, 1);
130 lua_pushvalue(l, 2);
131 while (1) {
132 // got a method or an attribute handler from this metatype
133 if (get_method_or_attr(l)) {
134 // clean up the stack
135 lua_insert(l, -3);
136 lua_pop(l, 2);
137 return 1;
138 }
139
140 // if there's no parent metatable, get out
141 lua_getfield(l, -2, "parent");
142 if (lua_isnil(l, -1))
143 break;
144
145 std::string parentName = lua_tostring(l, -1);
146 lua_pop(l, 1); // pop the parent name
147
148 lua_getfield(l, metaTypeRegistry, parentName.c_str());
149 if (lua_isnil(l, -1))
150 return luaL_error(l, "Encountered invalid parent metatype name %s", parentName.c_str());
151
152 lua_replace(l, -3); // replace the metatable with the parent
153 }
154
155 return 0;
156 }
157
l_newindex(lua_State * l)158 static int l_newindex(lua_State *l)
159 {
160 // userdata are typed, tables are not
161 bool typeless = lua_istable(l, 1);
162 assert(typeless || lua_isuserdata(l, 1));
163
164 // Attribute setters are not enabled for typeless objects.
165 if (typeless) {
166 lua_rawset(l, 1); // set the value in the table
167 return 0;
168 }
169
170 // Once we've dealt with the chance of a typeless object, the only thing
171 // left is userdata.
172
173 // first check properties. we don't need to drill through the metatype stack
174 // if the property is already available
175 lua_getuservalue(l, 1);
176
177 // Ensure the object already has the property defined
178 // Properties take precedence over attrs only if they've been previously set
179 // (use setprop if you want to be sure you're setting a property)
180 bool hasProperty = false;
181 if (!lua_isnil(l, -1)) {
182 lua_pushvalue(l, 2);
183 lua_gettable(l, -2);
184 hasProperty = !lua_isnil(l, -1);
185 lua_pop(l, 1);
186 }
187
188 // if the object is a valid propertied object, call its setters
189 if (hasProperty) {
190 auto *properties = LuaObjectBase::GetPropertiesFromObject(l, 1);
191
192 std::string name = lua_tostring(l, 2);
193 if (lua_isnumber(l, 3)) {
194 properties->Set(name, lua_tonumber(l, 2));
195 } else if (lua_isstring(l, 3)) {
196 properties->Set(name, lua_tostring(l, 3));
197 }
198
199 return 0;
200 }
201 lua_pop(l, 1);
202
203 // push the metatype registry here for later
204 lua_getfield(l, LUA_REGISTRYINDEX, "LuaMetaTypes");
205 int metaTypeRegistry = lua_gettop(l);
206
207 // Check the metatable for attributes
208 lua_getmetatable(l, 1); // get the metatable
209 while (true) {
210 // if we have an attribute handler, call it
211 // if the attribute entry is a non-function value, it is considered immutable
212 if (get_attr_entry(l, -1, 2)) {
213 if (lua_isfunction(l, -1)) {
214 lua_remove(l, -2); // remove the metatable
215 lua_pushvalue(l, 1); // push the self object
216 lua_pushvalue(l, 3); // push the value
217 pi_lua_protected_call(l, 2, 0);
218 return 0;
219 }
220 lua_pop(l, 1);
221 }
222
223 lua_getfield(l, -1, "parent"); // get the parent field from the metatable
224 if (lua_isnil(l, -1)) // hit the end of the chain, nothing here
225 break;
226
227 std::string parentName = lua_tostring(l, -1);
228 lua_pop(l, 1);
229
230 lua_getfield(l, metaTypeRegistry, parentName.c_str());
231 if (lua_isnil(l, -1))
232 return luaL_error(l, "Encountered invalid parent metatype name %s", parentName.c_str());
233
234 lua_remove(l, -2); // replace the metatable with the parent
235 }
236
237 return luaL_error(l, "Attempt to set undefined property %s on %s", lua_tostring(l, 2), lua_tostring(l, 1));
238 }
239
get_names_from_table(lua_State * l,std::vector<std::string> & names,const std::string & prefix,bool methodsOnly)240 static void get_names_from_table(lua_State *l, std::vector<std::string> &names, const std::string &prefix, bool methodsOnly)
241 {
242 lua_pushnil(l);
243 while (lua_next(l, -2)) {
244
245 // only include string keys. the . syntax doesn't work for anything
246 // else
247 if (lua_type(l, -2) != LUA_TSTRING) {
248 lua_pop(l, 1);
249 continue;
250 }
251
252 // only include callable things if requested
253 if (methodsOnly && lua_type(l, -1) != LUA_TFUNCTION) {
254 lua_pop(l, 1);
255 continue;
256 }
257
258 size_t str_size = 0;
259 const char *name = lua_tolstring(l, -2, &str_size);
260 // anything starting with an underscore is hidden
261 if (name[0] == '_') {
262 lua_pop(l, 1);
263 continue;
264 }
265
266 // check if the name begins with the prefix
267 if (strncmp(name, prefix.c_str(), std::min(str_size, prefix.size())) == 0)
268 // push back the portion of the name not matching the prefix (for completion)
269 names.push_back(std::string(name + prefix.size()));
270
271 lua_pop(l, 1);
272 }
273 }
274
GetNames(std::vector<std::string> & names,const std::string & prefix,bool methodsOnly)275 void LuaMetaTypeBase::GetNames(std::vector<std::string> &names, const std::string &prefix, bool methodsOnly)
276 {
277 // never show hidden names
278 if (!prefix.empty() && prefix[0] == '_')
279 return;
280
281 lua_State *l = Lua::manager->GetLuaState();
282
283 LUA_DEBUG_START(l);
284
285 // work out if/how we can deal with the value
286 bool typeless;
287 if (lua_istable(l, -1))
288 // we can always look into tables
289 typeless = true;
290
291 else if (lua_isuserdata(l, -1)) {
292 // two known types of userdata
293 // - LuaObject, metatable has a "type" field
294 // - RO table proxy, no type
295 lua_getmetatable(l, -1);
296 lua_getfield(l, -1, "type");
297 typeless = lua_isnil(l, -1);
298 lua_pop(l, 2);
299 }
300
301 else {
302 // it's a non-table object with a metatable
303 // this realistically can only be a string, but let's work with it anyways
304 lua_getmetatable(l, -1);
305 if (lua_isnil(l, -1)) {
306 lua_pop(l, 1);
307 return; // if no metatable, can't do anything
308 }
309
310 lua_getfield(l, -1, "__index");
311 typeless = lua_istable(l, -1);
312 lua_pop(l, 2);
313
314 // if the __index field isn't a table, we can't introspect into it
315 if (!typeless)
316 return;
317 }
318
319 LUA_DEBUG_CHECK(l, 0);
320
321 if (typeless) {
322 // if the object we're getting names from is a table, search it for names
323 if (lua_istable(l, -1))
324 get_names_from_table(l, names, prefix, methodsOnly);
325
326 // Check the metatable indexes
327 // Get the metatable of the object, then check it for an __index field
328 lua_pushvalue(l, -1);
329 while (lua_getmetatable(l, -1)) {
330 lua_getfield(l, -2, "__index");
331
332 // Replace the metatable with the index table to keep a stable stack size.
333 lua_replace(l, -3);
334 lua_pop(l, 1);
335
336 if (lua_istable(l, -1))
337 get_names_from_table(l, names, prefix, methodsOnly);
338 else
339 break;
340 }
341 lua_pop(l, 1);
342
343 return;
344 }
345
346 // properties
347 if (!methodsOnly) {
348 lua_getuservalue(l, -1);
349 if (!lua_isnil(l, -1))
350 get_names_from_table(l, names, prefix, false);
351 lua_pop(l, 1);
352 }
353
354 // check the metatable (and its parents) for methods and attributes
355 lua_getmetatable(l, -1);
356 while (1) {
357 lua_getfield(l, -1, "methods");
358 get_names_from_table(l, names, prefix, methodsOnly);
359 lua_pop(l, 1);
360
361 if (!methodsOnly) {
362 lua_getfield(l, -1, "attrs");
363 get_names_from_table(l, names, prefix, false);
364 lua_pop(l, 1);
365 }
366
367 lua_getfield(l, -1, "parent");
368 std::string parent = lua_tostring(l, -1);
369 if (!lua_isnil(l, -1))
370 parent = lua_tostring(l, -1);
371
372 lua_pop(l, 1); // pop the parent's name
373
374 bool hasParent = !parent.empty() && GetMetatableFromName(l, parent.c_str());
375 if (hasParent)
376 lua_remove(l, -2); // replace the previous metatype with the parent
377 else
378 break; // the old metatype is the only thing on the stack
379 }
380
381 lua_pop(l, 1); // pop the metatable
382
383 LUA_DEBUG_END(l, 0);
384 }
385
GetMetatableFromName(lua_State * l,const char * name)386 bool LuaMetaTypeBase::GetMetatableFromName(lua_State *l, const char *name)
387 {
388 luaL_getsubtable(l, LUA_REGISTRYINDEX, "LuaMetaTypes");
389 lua_getfield(l, -1, name);
390 lua_remove(l, -2);
391 if (lua_isnil(l, -1)) {
392 lua_pop(l, 1);
393 return false;
394 }
395
396 return true;
397 }
398
GetMetatable() const399 void LuaMetaTypeBase::GetMetatable() const
400 {
401 assert(IsValid());
402 LUA_DEBUG_START(m_lua);
403
404 luaL_getsubtable(m_lua, LUA_REGISTRYINDEX, "LuaMetaTypes");
405 lua_rawgeti(m_lua, -1, m_ref);
406 lua_remove(m_lua, -2);
407 assert(lua_type(m_lua, -1) == LUA_TTABLE);
408
409 LUA_DEBUG_END(m_lua, 1);
410 }
411
CreateMetaType(lua_State * l)412 void LuaMetaTypeBase::CreateMetaType(lua_State *l)
413 {
414 luaL_getsubtable(l, LUA_REGISTRYINDEX, "LuaMetaTypes");
415 m_lua = l;
416
417 // if the type name is empty, we're creating a "throwaway" object with no parent either
418 if (!m_typeName.empty()) {
419 // Warn if we're double-initializing a type name
420 lua_getfield(l, -1, m_typeName.c_str());
421 if (!lua_isnil(l, -1))
422 Output("Double-initialization of lua metatype %s. Do you have a name conflict?\n", m_typeName.c_str());
423 lua_pop(l, 1);
424 }
425
426 lua_newtable(l);
427
428 if (!m_typeName.empty()) {
429 // Set the entry LuaMetaTypes[typename] = metatype
430 lua_pushvalue(l, -1);
431 lua_setfield(l, -3, m_typeName.c_str());
432 }
433
434 // Store this metatable via a numeric ref index for easy access
435 lua_pushvalue(l, -1);
436 m_ref = luaL_ref(l, -3);
437
438 // set the type name on the metatable
439 lua_pushstring(l, m_typeName.c_str());
440 lua_setfield(l, -2, "type");
441
442 if (!m_parent.empty()) {
443 lua_pushstring(l, m_parent.c_str());
444 lua_setfield(l, -2, "parent");
445 }
446
447 // create the attributes table
448 lua_newtable(l);
449 lua_setfield(l, -2, "attrs");
450
451 // create the methods table
452 lua_newtable(l);
453 lua_setfield(l, -2, "methods");
454
455 lua_pushcclosure(l, &l_index, 0);
456 lua_setfield(l, -2, "__index");
457
458 lua_pushcclosure(l, &l_newindex, 0);
459 lua_setfield(l, -2, "__newindex");
460
461 // replace the LuaMetaTypes registry table, leaving the created metatype on the stack
462 lua_replace(l, -2);
463 }
464