aboutsummaryrefslogtreecommitdiff
path: root/src/tools.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools.cpp')
-rw-r--r--src/tools.cpp316
1 files changed, 73 insertions, 243 deletions
diff --git a/src/tools.cpp b/src/tools.cpp
index d270aac..e9114e0 100644
--- a/src/tools.cpp
+++ b/src/tools.cpp
@@ -89,28 +89,32 @@ static constexpr int kWriterReturnCode{ 666 };
89 89
90// ################################################################################################# 90// #################################################################################################
91 91
92// inspired from tconcat() in ltablib.c 92namespace tools {
93[[nodiscard]] static std::string_view luaG_pushFQN(lua_State* L_, int t_, int last_) 93
94{ 94 // inspired from tconcat() in ltablib.c
95 luaL_Buffer _b; 95 [[nodiscard]] std::string_view PushFQN(lua_State* L_, int t_, int last_)
96 STACK_CHECK_START_REL(L_, 0); 96 {
97 // Lua 5.4 pushes &b as light userdata on the stack. be aware of it... 97 luaL_Buffer _b;
98 luaL_buffinit(L_, &_b); // L_: ... {} ... &b? 98 STACK_CHECK_START_REL(L_, 0);
99 int _i{ 1 }; 99 // Lua 5.4 pushes &b as light userdata on the stack. be aware of it...
100 for (; _i < last_; ++_i) { 100 luaL_buffinit(L_, &_b); // L_: ... {} ... &b?
101 lua_rawgeti(L_, t_, _i); 101 int _i{ 1 };
102 luaL_addvalue(&_b); 102 for (; _i < last_; ++_i) {
103 luaL_addlstring(&_b, "/", 1); 103 lua_rawgeti(L_, t_, _i);
104 } 104 luaL_addvalue(&_b);
105 if (_i == last_) { // add last value (if interval was not empty) 105 luaL_addlstring(&_b, "/", 1);
106 lua_rawgeti(L_, t_, _i); 106 }
107 luaL_addvalue(&_b); 107 if (_i == last_) { // add last value (if interval was not empty)
108 lua_rawgeti(L_, t_, _i);
109 luaL_addvalue(&_b);
110 }
111 // &b is popped at that point (-> replaced by the result)
112 luaL_pushresult(&_b); // L_: ... {} ... "<result>"
113 STACK_CHECK(L_, 1);
114 return lua_tostringview(L_, -1);
108 } 115 }
109 // &b is popped at that point (-> replaced by the result) 116
110 luaL_pushresult(&_b); // L_: ... {} ... "<result>" 117} // namespace tools
111 STACK_CHECK(L_, 1);
112 return lua_tostringview(L_, -1);
113}
114 118
115// ################################################################################################# 119// #################################################################################################
116 120
@@ -144,7 +148,7 @@ static void update_lookup_entry(lua_State* L_, int ctxBase_, int depth_)
144 ++depth_; 148 ++depth_;
145 lua_rawseti(L_, _fqn, depth_); // L_: ... {bfc} k o name? 149 lua_rawseti(L_, _fqn, depth_); // L_: ... {bfc} k o name?
146 // generate name 150 // generate name
147 std::string_view const _newName{ luaG_pushFQN(L_, _fqn, depth_) }; // L_: ... {bfc} k o name? "f.q.n" 151 std::string_view const _newName{ tools::PushFQN(L_, _fqn, depth_) }; // L_: ... {bfc} k o name? "f.q.n"
148 // Lua 5.2 introduced a hash randomizer seed which causes table iteration to yield a different key order 152 // Lua 5.2 introduced a hash randomizer seed which causes table iteration to yield a different key order
149 // on different VMs even when the tables are populated the exact same way. 153 // on different VMs even when the tables are populated the exact same way.
150 // When Lua is built with compatibility options (such as LUA_COMPAT_ALL), 154 // When Lua is built with compatibility options (such as LUA_COMPAT_ALL),
@@ -299,233 +303,59 @@ static void populate_func_lookup_table_recur(lua_State* L_, int dbIdx_, int i_,
299 303
300// ################################################################################################# 304// #################################################################################################
301 305
302// create a "fully.qualified.name" <-> function equivalence database 306namespace tools {
303void populate_func_lookup_table(lua_State* const L_, int const i_, std::string_view const& name_)
304{
305 int const _in_base{ lua_absindex(L_, i_) };
306 DEBUGSPEW_CODE(Universe* _U = universe_get(L_));
307 std::string_view _name{ name_.empty() ? std::string_view{} : name_ };
308 DEBUGSPEW_CODE(DebugSpew(_U) << L_ << ": populate_func_lookup_table('" << _name << "')" << std::endl);
309 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U });
310 STACK_GROW(L_, 3);
311 STACK_CHECK_START_REL(L_, 0);
312 kLookupRegKey.pushValue(L_); // L_: {}
313 int const _dbIdx{ lua_gettop(L_) };
314 STACK_CHECK(L_, 1);
315 LUA_ASSERT(L_, lua_istable(L_, -1));
316 if (lua_type(L_, _in_base) == LUA_TFUNCTION) { // for example when a module is a simple function
317 if (_name.empty()) {
318 _name = "nullptr";
319 }
320 lua_pushvalue(L_, _in_base); // L_: {} f
321 std::ignore = lua_pushstringview(L_, _name); // L_: {} f name_
322 lua_rawset(L_, -3); // L_: {}
323 std::ignore = lua_pushstringview(L_, _name); // L_: {} name_
324 lua_pushvalue(L_, _in_base); // L_: {} name_ f
325 lua_rawset(L_, -3); // L_: {}
326 lua_pop(L_, 1); // L_:
327 } else if (lua_type(L_, _in_base) == LUA_TTABLE) {
328 lua_newtable(L_); // L_: {} {fqn}
329 int _startDepth{ 0 };
330 if (!_name.empty()) {
331 STACK_CHECK(L_, 2);
332 std::ignore = lua_pushstringview(L_, _name); // L_: {} {fqn} "name"
333 // generate a name, and if we already had one name, keep whichever is the shorter
334 lua_pushvalue(L_, _in_base); // L_: {} {fqn} "name" t
335 update_lookup_entry(L_, _dbIdx, _startDepth); // L_: {} {fqn} "name"
336 // don't forget to store the name at the bottom of the fqn stack
337 lua_rawseti(L_, -2, ++_startDepth); // L_: {} {fqn}
338 STACK_CHECK(L_, 2);
339 }
340 // retrieve the cache, create it if we haven't done it yet
341 std::ignore = kLookupCacheRegKey.getSubTable(L_, 0, 0); // L_: {} {fqn} {cache}
342 // process everything we find in that table, filling in lookup data for all functions and tables we see there
343 populate_func_lookup_table_recur(L_, _dbIdx, _in_base, _startDepth);
344 lua_pop(L_, 3); // L_:
345 } else {
346 lua_pop(L_, 1); // L_:
347 raise_luaL_error(L_, "unsupported module type %s", lua_typename(L_, lua_type(L_, _in_base)));
348 }
349 STACK_CHECK(L_, 0);
350}
351
352// #################################################################################################
353 307
354// Return some name helping to identify an object 308 // create a "fully.qualified.name" <-> function equivalence database
355[[nodiscard]] static int DiscoverObjectNameRecur(lua_State* L_, int shortest_, int depth_) 309 void PopulateFuncLookupTable(lua_State* const L_, int const i_, std::string_view const& name_)
356{ 310 {
357 static constexpr int kWhat{ 1 }; // the object to investigate // L_: o "r" {c} {fqn} ... {?} 311 int const _in_base{ lua_absindex(L_, i_) };
358 static constexpr int kResult{ 2 }; // where the result string is stored 312 DEBUGSPEW_CODE(Universe* _U = universe_get(L_));
359 static constexpr int kCache{ 3 }; // a cache 313 std::string_view _name{ name_.empty() ? std::string_view{} : name_ };
360 static constexpr int kFQN{ 4 }; // the name compositing stack 314 DEBUGSPEW_CODE(DebugSpew(_U) << L_ << ": PopulateFuncLookupTable('" << _name << "')" << std::endl);
361 // no need to scan this table if the name we will discover is longer than one we already know 315 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U });
362 if (shortest_ <= depth_ + 1) { 316 STACK_GROW(L_, 3);
363 return shortest_; 317 STACK_CHECK_START_REL(L_, 0);
364 } 318 kLookupRegKey.pushValue(L_); // L_: {}
365 STACK_GROW(L_, 3); 319 int const _dbIdx{ lua_gettop(L_) };
366 STACK_CHECK_START_REL(L_, 0); 320 STACK_CHECK(L_, 1);
367 // stack top contains the table to search in 321 LUA_ASSERT(L_, lua_istable(L_, -1));
368 lua_pushvalue(L_, -1); // L_: o "r" {c} {fqn} ... {?} {?} 322 if (lua_type(L_, _in_base) == LUA_TFUNCTION) { // for example when a module is a simple function
369 lua_rawget(L_, kCache); // L_: o "r" {c} {fqn} ... {?} nil/1 323 if (_name.empty()) {
370 // if table is already visited, we are done 324 _name = "nullptr";
371 if (!lua_isnil(L_, -1)) {
372 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?}
373 return shortest_;
374 }
375 // examined table is not in the cache, add it now
376 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?}
377 lua_pushvalue(L_, -1); // L_: o "r" {c} {fqn} ... {?} {?}
378 lua_pushinteger(L_, 1); // L_: o "r" {c} {fqn} ... {?} {?} 1
379 lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... {?}
380 // scan table contents
381 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} nil
382 while (lua_next(L_, -2)) { // L_: o "r" {c} {fqn} ... {?} k v
383 // std::string_view const _strKey{ (lua_type(L_, -2) == LUA_TSTRING) ? lua_tostringview(L_, -2) : "" }; // only for debugging
384 // lua_Number const numKey = (lua_type(L_, -2) == LUA_TNUMBER) ? lua_tonumber(L_, -2) : -6666; // only for debugging
385 STACK_CHECK(L_, 2);
386 // append key name to fqn stack
387 ++depth_;
388 lua_pushvalue(L_, -2); // L_: o "r" {c} {fqn} ... {?} k v k
389 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v
390 if (lua_rawequal(L_, -1, kWhat)) { // is it what we are looking for?
391 STACK_CHECK(L_, 2);
392 // update shortest name
393 if (depth_ < shortest_) {
394 shortest_ = depth_;
395 std::ignore = luaG_pushFQN(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v "fqn"
396 lua_replace(L_, kResult); // L_: o "r" {c} {fqn} ... {?} k v
397 }
398 // no need to search further at this level
399 lua_pop(L_, 2); // L_: o "r" {c} {fqn} ... {?}
400 STACK_CHECK(L_, 0);
401 break;
402 }
403 switch (lua_type(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k v
404 default: // nil, boolean, light userdata, number and string aren't identifiable
405 break;
406
407 case LUA_TTABLE: // L_: o "r" {c} {fqn} ... {?} k {}
408 STACK_CHECK(L_, 2);
409 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
410 // search in the table's metatable too
411 if (lua_getmetatable(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k {} {mt}
412 if (lua_istable(L_, -1)) {
413 ++depth_;
414 lua_pushliteral(L_, "__metatable"); // L_: o "r" {c} {fqn} ... {?} k {} {mt} "__metatable"
415 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k {} {mt}
416 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
417 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k {} {mt} nil
418 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k {} {mt}
419 --depth_;
420 }
421 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k {}
422 }
423 STACK_CHECK(L_, 2);
424 break;
425
426 case LUA_TTHREAD: // L_: o "r" {c} {fqn} ... {?} k T
427 // TODO: explore the thread's stack frame looking for our culprit?
428 break;
429
430 case LUA_TUSERDATA: // L_: o "r" {c} {fqn} ... {?} k U
431 STACK_CHECK(L_, 2);
432 // search in the object's metatable (some modules are built that way)
433 if (lua_getmetatable(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k U {mt}
434 if (lua_istable(L_, -1)) {
435 ++depth_;
436 lua_pushliteral(L_, "__metatable"); // L_: o "r" {c} {fqn} ... {?} k U {mt} "__metatable"
437 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k U {mt}
438 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
439 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k U {mt} nil
440 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k U {mt}
441 --depth_;
442 }
443 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U
444 } 325 }
445 STACK_CHECK(L_, 2); 326 lua_pushvalue(L_, _in_base); // L_: {} f
446 // search in the object's uservalues 327 std::ignore = lua_pushstringview(L_, _name); // L_: {} f name_
447 { 328 lua_rawset(L_, -3); // L_: {}
448 int _uvi{ 1 }; 329 std::ignore = lua_pushstringview(L_, _name); // L_: {} name_
449 while (lua_getiuservalue(L_, -1, _uvi) != LUA_TNONE) { // L_: o "r" {c} {fqn} ... {?} k U {u} 330 lua_pushvalue(L_, _in_base); // L_: {} name_ f
450 if (lua_istable(L_, -1)) { // if it is a table, look inside 331 lua_rawset(L_, -3); // L_: {}
451 ++depth_; 332 lua_pop(L_, 1); // L_:
452 lua_pushliteral(L_, "uservalue"); // L_: o "r" {c} {fqn} ... {?} k v {u} "uservalue" 333 } else if (lua_type(L_, _in_base) == LUA_TTABLE) {
453 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v {u} 334 lua_newtable(L_); // L_: {} {fqn}
454 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_); 335 int _startDepth{ 0 };
455 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k v {u} nil 336 if (!_name.empty()) {
456 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v {u} 337 STACK_CHECK(L_, 2);
457 --depth_; 338 std::ignore = lua_pushstringview(L_, _name); // L_: {} {fqn} "name"
458 } 339 // generate a name, and if we already had one name, keep whichever is the shorter
459 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U 340 lua_pushvalue(L_, _in_base); // L_: {} {fqn} "name" t
460 ++_uvi; 341 update_lookup_entry(L_, _dbIdx, _startDepth); // L_: {} {fqn} "name"
461 } 342 // don't forget to store the name at the bottom of the fqn stack
462 // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now 343 lua_rawseti(L_, -2, ++_startDepth); // L_: {} {fqn}
463 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U 344 STACK_CHECK(L_, 2);
464 } 345 }
465 STACK_CHECK(L_, 2); 346 // retrieve the cache, create it if we haven't done it yet
466 break; 347 std::ignore = kLookupCacheRegKey.getSubTable(L_, 0, 0); // L_: {} {fqn} {cache}
348 // process everything we find in that table, filling in lookup data for all functions and tables we see there
349 populate_func_lookup_table_recur(L_, _dbIdx, _in_base, _startDepth);
350 lua_pop(L_, 3); // L_:
351 } else {
352 lua_pop(L_, 1); // L_:
353 raise_luaL_error(L_, "unsupported module type %s", lua_typename(L_, lua_type(L_, _in_base)));
467 } 354 }
468 // make ready for next iteration 355 STACK_CHECK(L_, 0);
469 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k
470 // remove name from fqn stack
471 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k nil
472 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k
473 STACK_CHECK(L_, 1);
474 --depth_;
475 } // L_: o "r" {c} {fqn} ... {?}
476 STACK_CHECK(L_, 0);
477 // remove the visited table from the cache, in case a shorter path to the searched object exists
478 lua_pushvalue(L_, -1); // L_: o "r" {c} {fqn} ... {?} {?}
479 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} {?} nil
480 lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... {?}
481 STACK_CHECK(L_, 0);
482 return shortest_;
483}
484
485// #################################################################################################
486
487// "type", "name" = lanes.nameof(o)
488int luaG_nameof(lua_State* L_)
489{
490 int const _what{ lua_gettop(L_) };
491 if (_what > 1) {
492 raise_luaL_argerror(L_, _what, "too many arguments.");
493 }
494
495 // nil, boolean, light userdata, number and string aren't identifiable
496 if (lua_type(L_, 1) < LUA_TTABLE) {
497 lua_pushstring(L_, luaL_typename(L_, 1)); // L_: o "type"
498 lua_insert(L_, -2); // L_: "type" o
499 return 2;
500 } 356 }
501 357
502 STACK_GROW(L_, 4); 358} // namespace tools
503 STACK_CHECK_START_REL(L_, 0);
504 // this slot will contain the shortest name we found when we are done
505 lua_pushnil(L_); // L_: o nil
506 // push a cache that will contain all already visited tables
507 lua_newtable(L_); // L_: o nil {c}
508 // push a table whose contents are strings that, when concatenated, produce unique name
509 lua_newtable(L_); // L_: o nil {c} {fqn}
510 // {fqn}[1] = "_G"
511 lua_pushliteral(L_, LUA_GNAME); // L_: o nil {c} {fqn} "_G"
512 lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn}
513 // this is where we start the search
514 lua_pushglobaltable(L_); // L_: o nil {c} {fqn} _G
515 std::ignore = DiscoverObjectNameRecur(L_, std::numeric_limits<int>::max(), 1);
516 if (lua_isnil(L_, 2)) { // try again with registry, just in case...
517 lua_pop(L_, 1); // L_: o nil {c} {fqn}
518 lua_pushliteral(L_, "_R"); // L_: o nil {c} {fqn} "_R"
519 lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn}
520 lua_pushvalue(L_, LUA_REGISTRYINDEX); // L_: o nil {c} {fqn} _R
521 std::ignore = DiscoverObjectNameRecur(L_, std::numeric_limits<int>::max(), 1);
522 }
523 lua_pop(L_, 3); // L_: o "result"
524 STACK_CHECK(L_, 1);
525 lua_pushstring(L_, luaL_typename(L_, 1)); // L_: o "result" "type"
526 lua_replace(L_, -3); // L_: "type" "result"
527 return 2;
528}
529 359
530// ################################################################################################# 360// #################################################################################################
531 361