diff options
Diffstat (limited to 'src/tools.cpp')
-rw-r--r-- | src/tools.cpp | 316 |
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 | 92 | namespace 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 | 306 | namespace tools { |
303 | void 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) | ||
488 | int 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 | ||