aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-05-29 14:27:17 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-05-29 14:27:17 +0200
commit4007dbf5a2262a7c4f2f26089071942dde7c3a91 (patch)
treea53df94b2d640d9b374241fdec8905864a012c01 /src
parent5890678289e28cc9e666c1dda8265712bd27ac03 (diff)
downloadlanes-4007dbf5a2262a7c4f2f26089071942dde7c3a91.tar.gz
lanes-4007dbf5a2262a7c4f2f26089071942dde7c3a91.tar.bz2
lanes-4007dbf5a2262a7c4f2f26089071942dde7c3a91.zip
Moved implementation of lanes.nameof in a separate file
Diffstat (limited to 'src')
-rw-r--r--src/Makefile2
-rw-r--r--src/intercopycontext.cpp5
-rw-r--r--src/lane.cpp4
-rw-r--r--src/lanes.cpp13
-rw-r--r--src/nameof.cpp208
-rw-r--r--src/nameof.h5
-rw-r--r--src/state.cpp4
-rw-r--r--src/tools.cpp316
-rw-r--r--src/tools.h18
9 files changed, 309 insertions, 266 deletions
diff --git a/src/Makefile b/src/Makefile
index a01f7c3..0362b8b 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -7,7 +7,7 @@
7 7
8MODULE=lanes 8MODULE=lanes
9 9
10SRC=cancel.cpp compat.cpp deep.cpp intercopycontext.cpp keeper.cpp lane.cpp lanes.cpp linda.cpp lindafactory.cpp state.cpp threading.cpp tools.cpp tracker.cpp universe.cpp 10SRC=cancel.cpp compat.cpp deep.cpp intercopycontext.cpp keeper.cpp lane.cpp lanes.cpp linda.cpp lindafactory.cpp nameof.cpp state.cpp threading.cpp tools.cpp tracker.cpp universe.cpp
11 11
12OBJ=$(SRC:.cpp=.o) 12OBJ=$(SRC:.cpp=.o)
13 13
diff --git a/src/intercopycontext.cpp b/src/intercopycontext.cpp
index 8058838..044b197 100644
--- a/src/intercopycontext.cpp
+++ b/src/intercopycontext.cpp
@@ -31,6 +31,7 @@ THE SOFTWARE.
31#include "keeper.h" 31#include "keeper.h"
32#include "lane.h" 32#include "lane.h"
33#include "linda.h" 33#include "linda.h"
34#include "nameof.h"
34#include "universe.h" 35#include "universe.h"
35 36
36// ################################################################################################# 37// #################################################################################################
@@ -109,8 +110,8 @@ THE SOFTWARE.
109 // try to discover the name of the function we want to send 110 // try to discover the name of the function we want to send
110 kLaneNameRegKey.pushValue(L1); // L1: ... v ... lane_name 111 kLaneNameRegKey.pushValue(L1); // L1: ... v ... lane_name
111 char const* _from{ lua_tostring(L1, -1) }; 112 char const* _from{ lua_tostring(L1, -1) };
112 lua_pushcfunction(L1, luaG_nameof); // L1: ... v ... lane_name luaG_nameof 113 lua_pushcfunction(L1, LG_nameof); // L1: ... v ... lane_name LG_nameof
113 lua_pushvalue(L1, L1_i); // L1: ... v ... lane_name luaG_nameof t 114 lua_pushvalue(L1, L1_i); // L1: ... v ... lane_name LG_nameof t
114 lua_call(L1, 1, 2); // L1: ... v ... lane_name "type" "name"|nil 115 lua_call(L1, 1, 2); // L1: ... v ... lane_name "type" "name"|nil
115 char const* _typewhat{ (lua_type(L1, -2) == LUA_TSTRING) ? lua_tostring(L1, -2) : luaL_typename(L1, -2) }; 116 char const* _typewhat{ (lua_type(L1, -2) == LUA_TSTRING) ? lua_tostring(L1, -2) : luaL_typename(L1, -2) };
116 // second return value can be nil if the table was not found 117 // second return value can be nil if the table was not found
diff --git a/src/lane.cpp b/src/lane.cpp
index f4d4bd1..79ea028 100644
--- a/src/lane.cpp
+++ b/src/lane.cpp
@@ -642,7 +642,7 @@ static void PrepareLaneHelpers(Lane* lane_)
642 lua_State* const _L{ lane_->L }; 642 lua_State* const _L{ lane_->L };
643 // Tie "set_finalizer()" to the state 643 // Tie "set_finalizer()" to the state
644 lua_pushcfunction(_L, LG_set_finalizer); 644 lua_pushcfunction(_L, LG_set_finalizer);
645 populate_func_lookup_table(_L, -1, "set_finalizer"); 645 tools::PopulateFuncLookupTable(_L, -1, "set_finalizer");
646 lua_setglobal(_L, "set_finalizer"); 646 lua_setglobal(_L, "set_finalizer");
647 647
648 // Tie "set_debug_threadname()" to the state 648 // Tie "set_debug_threadname()" to the state
@@ -653,7 +653,7 @@ static void PrepareLaneHelpers(Lane* lane_)
653 653
654 // Tie "cancel_test()" to the state 654 // Tie "cancel_test()" to the state
655 lua_pushcfunction(_L, LG_cancel_test); 655 lua_pushcfunction(_L, LG_cancel_test);
656 populate_func_lookup_table(_L, -1, "cancel_test"); 656 tools::PopulateFuncLookupTable(_L, -1, "cancel_test");
657 lua_setglobal(_L, "cancel_test"); 657 lua_setglobal(_L, "cancel_test");
658} 658}
659 659
diff --git a/src/lanes.cpp b/src/lanes.cpp
index a82e4ba..04b0955 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -85,6 +85,7 @@ THE SOFTWARE.
85#include "intercopycontext.h" 85#include "intercopycontext.h"
86#include "keeper.h" 86#include "keeper.h"
87#include "lane.h" 87#include "lane.h"
88#include "nameof.h"
88#include "state.h" 89#include "state.h"
89#include "threading.h" 90#include "threading.h"
90#include "tools.h" 91#include "tools.h"
@@ -175,7 +176,7 @@ LUAG_FUNC(require)
175 lua_pushvalue(L_, lua_upvalueindex(1)); // L_: "name" ... require 176 lua_pushvalue(L_, lua_upvalueindex(1)); // L_: "name" ... require
176 lua_insert(L_, 1); // L_: require "name" ... 177 lua_insert(L_, 1); // L_: require "name" ...
177 lua_call(L_, _nargs, 1); // L_: module 178 lua_call(L_, _nargs, 1); // L_: module
178 populate_func_lookup_table(L_, -1, _name); 179 tools::PopulateFuncLookupTable(L_, -1, _name);
179 DEBUGSPEW_CODE(DebugSpew(_U) << "lanes.require '" << _name << "' END" << std::endl); 180 DEBUGSPEW_CODE(DebugSpew(_U) << "lanes.require '" << _name << "' END" << std::endl);
180 STACK_CHECK(L_, 0); 181 STACK_CHECK(L_, 0);
181 return 1; 182 return 1;
@@ -197,7 +198,7 @@ LUAG_FUNC(register)
197 STACK_CHECK_START_REL(L_, 0); // "name" mod_table 198 STACK_CHECK_START_REL(L_, 0); // "name" mod_table
198 DEBUGSPEW_CODE(DebugSpew(_U) << "lanes.register '" << _name << "' BEGIN" << std::endl); 199 DEBUGSPEW_CODE(DebugSpew(_U) << "lanes.register '" << _name << "' BEGIN" << std::endl);
199 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); 200 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U });
200 populate_func_lookup_table(L_, -1, _name); 201 tools::PopulateFuncLookupTable(L_, -1, _name);
201 DEBUGSPEW_CODE(DebugSpew(_U) << "lanes.register '" << _name << "' END" << std::endl); 202 DEBUGSPEW_CODE(DebugSpew(_U) << "lanes.register '" << _name << "' END" << std::endl);
202 STACK_CHECK(L_, 0); 203 STACK_CHECK(L_, 0);
203 return 0; 204 return 0;
@@ -414,7 +415,7 @@ LUAG_FUNC(lane_new)
414 } 415 }
415 // here the module was successfully required // L_: [fixed] args... n "modname" L2: ret 416 // here the module was successfully required // L_: [fixed] args... n "modname" L2: ret
416 // after requiring the module, register the functions it exported in our name<->function database 417 // after requiring the module, register the functions it exported in our name<->function database
417 populate_func_lookup_table(_L2, -1, _name); 418 tools::PopulateFuncLookupTable(_L2, -1, _name);
418 lua_pop(_L2, 1); // L_: [fixed] args... n "modname" L2: 419 lua_pop(_L2, 1); // L_: [fixed] args... n "modname" L2:
419 } 420 }
420 } 421 }
@@ -596,7 +597,7 @@ namespace {
596 { "wakeup_conv", LG_wakeup_conv }, 597 { "wakeup_conv", LG_wakeup_conv },
597 { "set_thread_priority", LG_set_thread_priority }, 598 { "set_thread_priority", LG_set_thread_priority },
598 { "set_thread_affinity", LG_set_thread_affinity }, 599 { "set_thread_affinity", LG_set_thread_affinity },
599 { "nameof", luaG_nameof }, 600 { "nameof", LG_nameof },
600 { "register", LG_register }, 601 { "register", LG_register },
601 { Universe::kFinally, Universe::InitializeFinalizer }, 602 { Universe::kFinally, Universe::InitializeFinalizer },
602 { "set_singlethreaded", LG_set_singlethreaded }, 603 { "set_singlethreaded", LG_set_singlethreaded },
@@ -742,7 +743,7 @@ LUAG_FUNC(configure)
742 // register all native functions found in that module in the transferable functions database 743 // register all native functions found in that module in the transferable functions database
743 // we process it before _G because we don't want to find the module when scanning _G (this would generate longer names) 744 // we process it before _G because we don't want to find the module when scanning _G (this would generate longer names)
744 // for example in package.loaded["lanes.core"].* 745 // for example in package.loaded["lanes.core"].*
745 populate_func_lookup_table(L_, -1, _name); 746 tools::PopulateFuncLookupTable(L_, -1, _name);
746 STACK_CHECK(L_, 2); 747 STACK_CHECK(L_, 2);
747 748
748 // record all existing C/JIT-fast functions 749 // record all existing C/JIT-fast functions
@@ -752,7 +753,7 @@ LUAG_FUNC(configure)
752 // because we will do it after on_state_create() is called, 753 // because we will do it after on_state_create() is called,
753 // and we don't want to skip _G because of caching in case globals are created then 754 // and we don't want to skip _G because of caching in case globals are created then
754 lua_pushglobaltable(L_); // L_: settings M _G 755 lua_pushglobaltable(L_); // L_: settings M _G
755 populate_func_lookup_table(L_, -1, {}); 756 tools::PopulateFuncLookupTable(L_, -1, {});
756 lua_pop(L_, 1); // L_: settings M 757 lua_pop(L_, 1); // L_: settings M
757 } 758 }
758 lua_pop(L_, 1); // L_: settings 759 lua_pop(L_, 1); // L_: settings
diff --git a/src/nameof.cpp b/src/nameof.cpp
new file mode 100644
index 0000000..7614577
--- /dev/null
+++ b/src/nameof.cpp
@@ -0,0 +1,208 @@
1/*
2===============================================================================
3
4Copyright (C) 2024 benoit Germain <bnt.germain@gmail.com>
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in
14all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22THE SOFTWARE.
23
24===============================================================================
25*/
26
27#include "nameof.h"
28
29#include "tools.h"
30
31// #################################################################################################
32
33// Return some name helping to identify an object
34[[nodiscard]] static int DiscoverObjectNameRecur(lua_State* L_, int shortest_, int depth_)
35{
36 static constexpr int kWhat{ 1 }; // the object to investigate // L_: o "r" {c} {fqn} ... {?}
37 static constexpr int kResult{ 2 }; // where the result string is stored
38 static constexpr int kCache{ 3 }; // a cache
39 static constexpr int kFQN{ 4 }; // the name compositing stack
40 // no need to scan this table if the name we will discover is longer than one we already know
41 if (shortest_ <= depth_ + 1) {
42 return shortest_;
43 }
44 STACK_GROW(L_, 3);
45 STACK_CHECK_START_REL(L_, 0);
46 // stack top contains the table to search in
47 lua_pushvalue(L_, -1); // L_: o "r" {c} {fqn} ... {?} {?}
48 lua_rawget(L_, kCache); // L_: o "r" {c} {fqn} ... {?} nil/1
49 // if table is already visited, we are done
50 if (!lua_isnil(L_, -1)) {
51 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?}
52 return shortest_;
53 }
54 // examined table is not in the cache, add it now
55 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?}
56 lua_pushvalue(L_, -1); // L_: o "r" {c} {fqn} ... {?} {?}
57 lua_pushinteger(L_, 1); // L_: o "r" {c} {fqn} ... {?} {?} 1
58 lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... {?}
59 // scan table contents
60 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} nil
61 while (lua_next(L_, -2)) { // L_: o "r" {c} {fqn} ... {?} k v
62 // std::string_view const _strKey{ (lua_type(L_, -2) == LUA_TSTRING) ? lua_tostringview(L_, -2) : "" }; // only for debugging
63 // lua_Number const numKey = (lua_type(L_, -2) == LUA_TNUMBER) ? lua_tonumber(L_, -2) : -6666; // only for debugging
64 STACK_CHECK(L_, 2);
65 // append key name to fqn stack
66 ++depth_;
67 lua_pushvalue(L_, -2); // L_: o "r" {c} {fqn} ... {?} k v k
68 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v
69 if (lua_rawequal(L_, -1, kWhat)) { // is it what we are looking for?
70 STACK_CHECK(L_, 2);
71 // update shortest name
72 if (depth_ < shortest_) {
73 shortest_ = depth_;
74 std::ignore = tools::PushFQN(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v "fqn"
75 lua_replace(L_, kResult); // L_: o "r" {c} {fqn} ... {?} k v
76 }
77 // no need to search further at this level
78 lua_pop(L_, 2); // L_: o "r" {c} {fqn} ... {?}
79 STACK_CHECK(L_, 0);
80 break;
81 }
82 switch (lua_type(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k v
83 default: // nil, boolean, light userdata, number and string aren't identifiable
84 break;
85
86 case LUA_TTABLE: // L_: o "r" {c} {fqn} ... {?} k {}
87 STACK_CHECK(L_, 2);
88 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
89 // search in the table's metatable too
90 if (lua_getmetatable(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k {} {mt}
91 if (lua_istable(L_, -1)) {
92 ++depth_;
93 lua_pushliteral(L_, "__metatable"); // L_: o "r" {c} {fqn} ... {?} k {} {mt} "__metatable"
94 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k {} {mt}
95 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
96 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k {} {mt} nil
97 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k {} {mt}
98 --depth_;
99 }
100 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k {}
101 }
102 STACK_CHECK(L_, 2);
103 break;
104
105 case LUA_TTHREAD: // L_: o "r" {c} {fqn} ... {?} k T
106 // TODO: explore the thread's stack frame looking for our culprit?
107 break;
108
109 case LUA_TUSERDATA: // L_: o "r" {c} {fqn} ... {?} k U
110 STACK_CHECK(L_, 2);
111 // search in the object's metatable (some modules are built that way)
112 if (lua_getmetatable(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k U {mt}
113 if (lua_istable(L_, -1)) {
114 ++depth_;
115 lua_pushliteral(L_, "__metatable"); // L_: o "r" {c} {fqn} ... {?} k U {mt} "__metatable"
116 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k U {mt}
117 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
118 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k U {mt} nil
119 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k U {mt}
120 --depth_;
121 }
122 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U
123 }
124 STACK_CHECK(L_, 2);
125 // search in the object's uservalues
126 {
127 int _uvi{ 1 };
128 while (lua_getiuservalue(L_, -1, _uvi) != LUA_TNONE) { // L_: o "r" {c} {fqn} ... {?} k U {u}
129 if (lua_istable(L_, -1)) { // if it is a table, look inside
130 ++depth_;
131 lua_pushliteral(L_, "uservalue"); // L_: o "r" {c} {fqn} ... {?} k v {u} "uservalue"
132 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v {u}
133 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
134 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k v {u} nil
135 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v {u}
136 --depth_;
137 }
138 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U
139 ++_uvi;
140 }
141 // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now
142 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U
143 }
144 STACK_CHECK(L_, 2);
145 break;
146 }
147 // make ready for next iteration
148 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k
149 // remove name from fqn stack
150 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k nil
151 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k
152 STACK_CHECK(L_, 1);
153 --depth_;
154 } // L_: o "r" {c} {fqn} ... {?}
155 STACK_CHECK(L_, 0);
156 // remove the visited table from the cache, in case a shorter path to the searched object exists
157 lua_pushvalue(L_, -1); // L_: o "r" {c} {fqn} ... {?} {?}
158 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} {?} nil
159 lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... {?}
160 STACK_CHECK(L_, 0);
161 return shortest_;
162}
163
164// #################################################################################################
165
166// "type", "name" = lanes.nameof(o)
167LUAG_FUNC(nameof)
168{
169 int const _what{ lua_gettop(L_) };
170 if (_what > 1) {
171 raise_luaL_argerror(L_, _what, "too many arguments.");
172 }
173
174 // nil, boolean, light userdata, number and string aren't identifiable
175 if (lua_type(L_, 1) < LUA_TTABLE) {
176 lua_pushstring(L_, luaL_typename(L_, 1)); // L_: o "type"
177 lua_insert(L_, -2); // L_: "type" o
178 return 2;
179 }
180
181 STACK_GROW(L_, 4);
182 STACK_CHECK_START_REL(L_, 0);
183 // this slot will contain the shortest name we found when we are done
184 lua_pushnil(L_); // L_: o nil
185 // push a cache that will contain all already visited tables
186 lua_newtable(L_); // L_: o nil {c}
187 // push a table whose contents are strings that, when concatenated, produce unique name
188 lua_newtable(L_); // L_: o nil {c} {fqn}
189 // {fqn}[1] = "_G"
190 lua_pushliteral(L_, LUA_GNAME); // L_: o nil {c} {fqn} "_G"
191 lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn}
192 // this is where we start the search
193 lua_pushglobaltable(L_); // L_: o nil {c} {fqn} _G
194 std::ignore = DiscoverObjectNameRecur(L_, std::numeric_limits<int>::max(), 1);
195 if (lua_isnil(L_, 2)) { // try again with registry, just in case...
196 lua_pop(L_, 1); // L_: o nil {c} {fqn}
197 lua_pushliteral(L_, "_R"); // L_: o nil {c} {fqn} "_R"
198 lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn}
199 lua_pushvalue(L_, LUA_REGISTRYINDEX); // L_: o nil {c} {fqn} _R
200 std::ignore = DiscoverObjectNameRecur(L_, std::numeric_limits<int>::max(), 1);
201 }
202 lua_pop(L_, 3); // L_: o "result"
203 STACK_CHECK(L_, 1);
204 lua_pushstring(L_, luaL_typename(L_, 1)); // L_: o "result" "type"
205 lua_replace(L_, -3); // L_: "type" "result"
206 return 2;
207}
208
diff --git a/src/nameof.h b/src/nameof.h
new file mode 100644
index 0000000..0e15a70
--- /dev/null
+++ b/src/nameof.h
@@ -0,0 +1,5 @@
1#pragma once
2
3#include "macros_and_utils.h"
4
5LUAG_FUNC(nameof);
diff --git a/src/state.cpp b/src/state.cpp
index 8520346..271e3a7 100644
--- a/src/state.cpp
+++ b/src/state.cpp
@@ -107,7 +107,7 @@ static void open1lib(lua_State* L_, std::string_view const& name_)
107 luaL_requiref(L_, _name.data(), _libfunc, !isLanesCore); // L_: {lib} 107 luaL_requiref(L_, _name.data(), _libfunc, !isLanesCore); // L_: {lib}
108 // lanes.core doesn't declare a global, so scan it here and now 108 // lanes.core doesn't declare a global, so scan it here and now
109 if (isLanesCore) { 109 if (isLanesCore) {
110 populate_func_lookup_table(L_, -1, _name); 110 tools::PopulateFuncLookupTable(L_, -1, _name);
111 } 111 }
112 lua_pop(L_, 1); // L_: 112 lua_pop(L_, 1); // L_:
113 STACK_CHECK(L_, 0); 113 STACK_CHECK(L_, 0);
@@ -340,7 +340,7 @@ lua_State* luaG_newstate(Universe* U_, SourceState from_, std::optional<std::str
340 STACK_CHECK(_L, 0); 340 STACK_CHECK(_L, 0);
341 // after all this, register everything we find in our name<->function database 341 // after all this, register everything we find in our name<->function database
342 lua_pushglobaltable(_L); // L: _G 342 lua_pushglobaltable(_L); // L: _G
343 populate_func_lookup_table(_L, -1, {}); 343 tools::PopulateFuncLookupTable(_L, -1, {});
344 lua_pop(_L, 1); // L: 344 lua_pop(_L, 1); // L:
345 STACK_CHECK(_L, 0); 345 STACK_CHECK(_L, 0);
346 346
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
diff --git a/src/tools.h b/src/tools.h
index 3659b42..e240fdb 100644
--- a/src/tools.h
+++ b/src/tools.h
@@ -22,18 +22,16 @@ enum class FuncSubType
22 22
23// ################################################################################################# 23// #################################################################################################
24 24
25[[nodiscard]] int luaG_nameof(lua_State* L_);
26
27void populate_func_lookup_table(lua_State* const L_, int const i_, std::string_view const& name_);
28
29namespace tools {
30 void SerializeRequire(lua_State* L_);
31} // namespace tools
32
33// #################################################################################################
34
35// xxh64 of string "kConfigRegKey" generated at https://www.pelock.com/products/hash-calculator 25// xxh64 of string "kConfigRegKey" generated at https://www.pelock.com/products/hash-calculator
36static constexpr RegistryUniqueKey kConfigRegKey{ 0x608379D20A398046ull }; // registry key to access the configuration 26static constexpr RegistryUniqueKey kConfigRegKey{ 0x608379D20A398046ull }; // registry key to access the configuration
37 27
38// xxh64 of string "kLookupRegKey" generated at https://www.pelock.com/products/hash-calculator 28// xxh64 of string "kLookupRegKey" generated at https://www.pelock.com/products/hash-calculator
39static constexpr RegistryUniqueKey kLookupRegKey{ 0xBF1FC5CF3C6DD47Bull }; // registry key to access the lookup database 29static constexpr RegistryUniqueKey kLookupRegKey{ 0xBF1FC5CF3C6DD47Bull }; // registry key to access the lookup database
30
31// #################################################################################################
32
33namespace tools {
34 void PopulateFuncLookupTable(lua_State* const L_, int const i_, std::string_view const& name_);
35 [[nodiscard]] std::string_view PushFQN(lua_State* L_, int t_, int last_);
36 void SerializeRequire(lua_State* L_);
37} // namespace tools