aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2025-03-07 17:44:59 +0100
committerBenoit Germain <benoit.germain@ubisoft.com>2025-03-07 17:44:59 +0100
commitaaef96a2faeb761b2a8b893158899f48c39cbf4b (patch)
treeb500548e19ce09e1695598af21d486765cc6441a
parentb92dbd75b1c9988dcc83e5876e1ce2815145fa30 (diff)
downloadlanes-aaef96a2faeb761b2a8b893158899f48c39cbf4b.tar.gz
lanes-aaef96a2faeb761b2a8b893158899f48c39cbf4b.tar.bz2
lanes-aaef96a2faeb761b2a8b893158899f48c39cbf4b.zip
Revamped lanes.nameof
-rw-r--r--src/nameof.cpp310
-rw-r--r--src/tools.cpp9
-rw-r--r--src/tools.hpp2
-rw-r--r--unit_tests/lane_tests.cpp15
4 files changed, 218 insertions, 118 deletions
diff --git a/src/nameof.cpp b/src/nameof.cpp
index b3874f3..1f338a5 100644
--- a/src/nameof.cpp
+++ b/src/nameof.cpp
@@ -31,137 +31,225 @@ THE SOFTWARE.
31 31
32// ################################################################################################# 32// #################################################################################################
33 33
34DECLARE_UNIQUE_TYPE(FqnLength, lua_Unsigned);
35
34// Return some name helping to identify an object 36// Return some name helping to identify an object
35[[nodiscard]] 37[[nodiscard]]
36static int DiscoverObjectNameRecur(lua_State* const L_, int shortest_, TableIndex const curDepth_) 38FqnLength DiscoverObjectNameRecur(lua_State* const L_, FqnLength const shortest_)
37{ 39{
38 static constexpr StackIndex kWhat{ 1 }; // the object to investigate // L_: o "r" {c} {fqn} ... {?} 40 static constexpr StackIndex kWhat{ 1 }; // the object to investigate // L_: o "r" {c} {fqn} ... <>
39 static constexpr StackIndex kResult{ 2 }; // where the result string is stored 41 static constexpr StackIndex kResult{ 2 }; // where the result string is stored
40 static constexpr StackIndex kCache{ 3 }; // a cache 42 static constexpr StackIndex kCache{ 3 }; // a cache, where visited locations remember the FqnLength to reach them
41 static constexpr StackIndex kFQN{ 4 }; // the name compositing stack 43 static constexpr StackIndex kFQN{ 4 }; // the name compositing stack
42 // no need to scan this table if the name we will discover is longer than one we already know 44
43 TableIndex const _nextDepth{ curDepth_ + 1 }; 45 // no need to scan this location if the name we will discover is longer than one we already know
44 if (shortest_ <= _nextDepth) { 46 FqnLength const _fqnLength{ lua_rawlen(L_, kFQN) };
47 if (shortest_ <= _fqnLength) {
45 return shortest_; 48 return shortest_;
46 } 49 }
47 50
48 auto _pushNameOnFQN = [L_](std::string_view const& name_, TableIndex const depth_) { 51 // in: k, v at the top of the stack
49 luaG_pushstring(L_, name_); // L_: o "r" {c} {fqn} ... name_ 52 static constexpr auto _pushNameOnFQN = [](lua_State* const L_) {
50 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {mt} 53 STACK_CHECK_START_REL(L_, 0);
54 lua_pushvalue(L_, -2); // L_: o "r" {c} {fqn} ... k v k
55 auto const _keyType{ luaG_type(L_, kIdxTop) };
56 if (_keyType != LuaType::STRING) {
57 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... k v
58 luaG_pushstring(L_, "<%s>", luaG_typename(L_, _keyType).data()); // L_: o "r" {c} {fqn} ... k v "<type of k>"
59 } else {
60 // decorate the key string with something that tells us the type of the value
61 switch (luaG_type(L_, StackIndex{ -2 })) {
62 default:
63 luaG_pushstring(L_, "??"); // L_: o "r" {c} {fqn} ... k v "k" "??"
64 break;
65 case LuaType::FUNCTION:
66 luaG_pushstring(L_, "()"); // L_: o "r" {c} {fqn} ... k v "k" "()"
67 break;
68 case LuaType::TABLE:
69 luaG_pushstring(L_, "[]"); // L_: o "r" {c} {fqn} ... k v "k" "[]"
70 break;
71 case LuaType::USERDATA:
72 luaG_pushstring(L_, "<>"); // L_: o "r" {c} {fqn} ... k v "k" "<>"
73 break;
74 }
75 lua_concat(L_, 2); // L_: o "r" {c} {fqn} ... k v "k??"
76 }
77
78 FqnLength const _depth{ lua_rawlen(L_, kFQN) + 1 };
79 lua_rawseti(L_, kFQN, _depth); // L_: o "r" {c} {fqn} ... k v
80 STACK_CHECK(L_, 0);
81 STACK_CHECK(L_, 0);
82 return _depth;
51 }; 83 };
52 84
53 auto _popNameFromFQN = [L_](TableIndex const depth_) { 85 static constexpr auto _popNameFromFQN = [](lua_State* const L_) {
86 STACK_CHECK_START_REL(L_, 0);
54 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... nil 87 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... nil
55 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... 88 lua_rawseti(L_, kFQN, lua_rawlen(L_, kFQN)); // L_: o "r" {c} {fqn} ...
89 STACK_CHECK(L_, 0);
56 }; 90 };
57 91
58 auto _recurseIfTableThenPop = [&_pushNameOnFQN, &_popNameFromFQN, L_](std::string_view const& name_, TableIndex const depth_, int shortest_) { 92 static constexpr auto _recurseThenPop = [](lua_State* const L_, FqnLength const shortest_) -> FqnLength {
59 STACK_CHECK_START_REL(L_, 0); // L_: o "r" {c} {fqn} ... <> {}? 93 STACK_CHECK_START_REL(L_, 0); // L_: o "r" {c} {fqn} ... <>
60 if (lua_istable(L_, kIdxTop)) { 94 FqnLength r_{ shortest_ };
61 _pushNameOnFQN(name_, depth_); 95 auto const _type{ luaG_type(L_, kIdxTop) };
62 shortest_ = DiscoverObjectNameRecur(L_, shortest_, TableIndex{ depth_ + 1 }); 96 if (_type == LuaType::TABLE || _type == LuaType::USERDATA || _type == LuaType::FUNCTION) {
63 _popNameFromFQN(depth_); 97 r_ = DiscoverObjectNameRecur(L_, shortest_);
98 STACK_CHECK(L_, 0);
99 _popNameFromFQN(L_);
100 STACK_CHECK(L_, 0);
64 } 101 }
65 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... <> 102
103 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ...
66 STACK_CHECK(L_, -1); 104 STACK_CHECK(L_, -1);
67 return shortest_; 105 return r_;
68 }; 106 };
69 107
70 STACK_GROW(L_, 3); 108 // in: k, v at the top of the stack
71 STACK_CHECK_START_REL(L_, 0); 109 // out: v popped from the stack
72 // stack top contains the table to search in 110 static constexpr auto _processKeyValue = [](lua_State* const L_, FqnLength const shortest_) -> FqnLength {
73 LUA_ASSERT(L_, lua_istable(L_, kIdxTop)); 111 FqnLength _r{ shortest_ };
74 lua_pushvalue(L_, kIdxTop); // L_: o "r" {c} {fqn} ... {?} {?} 112 STACK_GROW(L_, 2);
75 lua_rawget(L_, kCache); // L_: o "r" {c} {fqn} ... {?} nil/1 113 STACK_CHECK_START_REL(L_, 0); // L_: o "r" {c} {fqn} ... k v
76 // if table is already visited, we are done 114
77 if (!lua_isnil(L_, kIdxTop)) { 115 // filter out uninteresting values
78 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} 116 auto const _valType{ luaG_type(L_, kIdxTop) };
79 return shortest_; 117 if (_valType == LuaType::BOOLEAN || _valType == LuaType::LIGHTUSERDATA || _valType == LuaType::NUMBER || _valType == LuaType::STRING) {
80 } 118 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... k
81 // examined table is not in the cache, add it now 119 return _r;
82 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} 120 }
83 // cache[o] = 1 121
84 lua_pushvalue(L_, kIdxTop); // L_: o "r" {c} {fqn} ... {?} {?}
85 lua_pushinteger(L_, 1); // L_: o "r" {c} {fqn} ... {?} {?} 1
86 lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... {?}
87 // scan table contents
88 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} nil
89 while (lua_next(L_, -2)) { // L_: o "r" {c} {fqn} ... {?} k v
90 // std::string_view const _strKey{ (luaG_type(L_, -2) == LuaType::STRING) ? luaG_tostring(L_, -2) : "" }; // only for debugging
91 // lua_Number const numKey = (luaG_type(L_, -2) == LuaType::NUMBER) ? lua_tonumber(L_, -2) : -6666; // only for debugging
92 STACK_CHECK(L_, 2);
93 // append key name to fqn stack 122 // append key name to fqn stack
94 lua_pushvalue(L_, -2); // L_: o "r" {c} {fqn} ... {?} k v k 123 FqnLength const _depth{ _pushNameOnFQN(L_) }; // L_: o "r" {c} {fqn} ... k v
95 lua_rawseti(L_, kFQN, _nextDepth); // L_: o "r" {c} {fqn} ... {?} k v 124
125 // process the value
96 if (lua_rawequal(L_, kIdxTop, kWhat)) { // is it what we are looking for? 126 if (lua_rawequal(L_, kIdxTop, kWhat)) { // is it what we are looking for?
97 STACK_CHECK(L_, 2);
98 // update shortest name 127 // update shortest name
99 if (_nextDepth < shortest_) { 128 if (_depth < _r) {
100 shortest_ = _nextDepth; 129 _r = _depth;
101 std::ignore = tools::PushFQN(L_, kFQN, _nextDepth); // L_: o "r" {c} {fqn} ... {?} k v "fqn" 130 std::ignore = tools::PushFQN(L_, kFQN); // L_: o "r" {c} {fqn} ... k v "fqn"
102 lua_replace(L_, kResult); // L_: o "r" {c} {fqn} ... {?} k v 131 lua_replace(L_, kResult); // L_: o "r" {c} {fqn} ... k v
103 } 132 }
104 // no need to search further at this level 133
105 lua_pop(L_, 2); // L_: o "r" {c} {fqn} ... {?} 134 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... k
106 STACK_CHECK(L_, 0); 135 _popNameFromFQN(L_);
107 break; 136 } else {
137 // let's see if the value contains what we are looking for
138 _r = _recurseThenPop(L_, _r); // L_: o "r" {c} {fqn} ... k
108 } 139 }
109 switch (luaG_type(L_, kIdxTop)) { // L_: o "r" {c} {fqn} ... {?} k v
110 default: // nil, boolean, light userdata, number and string aren't identifiable
111 break;
112
113 case LuaType::TABLE: // L_: o "r" {c} {fqn} ... {?} k {}
114 STACK_CHECK(L_, 2);
115 shortest_ = DiscoverObjectNameRecur(L_, shortest_, _nextDepth);
116 // search in the table's metatable too
117 if (lua_getmetatable(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k {} {mt}
118 shortest_ = _recurseIfTableThenPop("__metatable", _nextDepth, shortest_); // L_: o "r" {c} {fqn} ... {?} k {}
119 }
120 STACK_CHECK(L_, 2);
121 break;
122
123 case LuaType::THREAD: // L_: o "r" {c} {fqn} ... {?} k T
124 // TODO: explore the thread's stack frame looking for our culprit?
125 break;
126
127 case LuaType::USERDATA: // L_: o "r" {c} {fqn} ... {?} k U
128 STACK_CHECK(L_, 2);
129 // search in the object's metatable (some modules are built that way)
130 if (lua_getmetatable(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k U {mt}
131 shortest_ = _recurseIfTableThenPop("__metatable", _nextDepth, shortest_); // L_: o "r" {c} {fqn} ... {?} k U
132 }
133 140
134 STACK_CHECK(L_, 2); 141 STACK_CHECK(L_, -1);
135 // search in the object's uservalues 142 return _r;
136 { 143 };
137 UserValueIndex _uvi{ 1 }; 144
138 while (lua_getiuservalue(L_, kIdxTop, _uvi) != LUA_TNONE) { // L_: o "r" {c} {fqn} ... {?} k U {u} 145 static constexpr auto _scanTable = [](lua_State* const L_, FqnLength const shortest_) -> FqnLength {
139 shortest_ = _recurseIfTableThenPop("uservalue", _nextDepth, shortest_); // L_: o "r" {c} {fqn} ... {?} k U 146 FqnLength r_{ shortest_ };
140 ++_uvi; 147 STACK_GROW(L_, 2);
141 } 148 STACK_CHECK_START_REL(L_, 0);
142 // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now 149 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} nil
143 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U 150 while (lua_next(L_, -2)) { // L_: o "r" {c} {fqn} ... {?} k v
151 r_ = _processKeyValue(L_, r_); // L_: o "r" {c} {fqn} ... {?} k
152 } // L_: o "r" {c} {fqn} ... {?}
153
154 if (lua_getmetatable(L_, kIdxTop)) { // L_: o "r" {c} {fqn} ... {?} {mt}
155 lua_pushstring(L_, "__metatable"); // L_: o "r" {c} {fqn} ... {?} {mt} "__metatable"
156 lua_insert(L_, -2); // L_: o "r" {c} {fqn} ... {?} "__metatable" {mt}
157 r_ = _processKeyValue(L_, r_); // L_: o "r" {c} {fqn} ... {?} "__metatable"
158 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?}
159 }
160
161 STACK_CHECK(L_, 0);
162 return r_;
163 };
164
165 static constexpr auto _scanUserData = [](lua_State* const L_, FqnLength const shortest_) -> FqnLength {
166 FqnLength r_{ shortest_ };
167 FqnLength const _depth{ lua_rawlen(L_, kFQN) + 1 };
168 STACK_GROW(L_, 2);
169 STACK_CHECK_START_REL(L_, 0);
170 if (lua_getmetatable(L_, kIdxTop)) { // L_: o "r" {c} {fqn} ... U {mt}
171 lua_pushstring(L_, "__metatable"); // L_: o "r" {c} {fqn} ... U {mt} "__metatable"
172 lua_insert(L_, -2); // L_: o "r" {c} {fqn} ... U "__metatable" {mt}
173 r_ = _processKeyValue(L_, r_); // L_: o "r" {c} {fqn} ... U "__metatable"
174 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... U
175 }
176
177 STACK_CHECK(L_, 0);
178
179 UserValueIndex _uvi{ 0 };
180 while (lua_getiuservalue(L_, kIdxTop, ++_uvi) != LUA_TNONE) { // L_: o "r" {c} {fqn} ... U uv
181 luaG_pushstring(L_, "<uv:%d>", _uvi); // L_: o "r" {c} {fqn} ... U uv name
182 lua_insert(L_, -2); // L_: o "r" {c} {fqn} ... U name uv
183 r_ = _processKeyValue(L_, r_); // L_: o "r" {c} {fqn} ... U name
184 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... U
185 }
186
187 // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now
188 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... U
189
190 STACK_CHECK(L_, 0);
191 return r_;
192 };
193
194 static constexpr auto _scanFunction = [](lua_State* const L_, FqnLength const shortest_) -> FqnLength {
195 FqnLength r_{ shortest_ };
196 STACK_GROW(L_, 2);
197 STACK_CHECK_START_REL(L_, 0); // L_: o "r" {c} {fqn} ... F
198 int _n{ 0 };
199 for (char const* _upname{}; (_upname = lua_getupvalue(L_, kIdxTop, ++_n));) { // L_: o "r" {c} {fqn} ... F up
200 if (*_upname == 0) {
201 _upname = "<C>";
144 } 202 }
145 STACK_CHECK(L_, 2);
146 break;
147 203
148 case LuaType::FUNCTION: // L_: o "r" {c} {fqn} ... {?} k F 204 luaG_pushstring(L_, "upvalue:%s", _upname); // L_: o "r" {c} {fqn} ... F up name
149 // TODO: explore the function upvalues 205 lua_insert(L_, -2); // L_: o "r" {c} {fqn} ... F name up
150 break; 206 r_ = _processKeyValue(L_, r_); // L_: o "r" {c} {fqn} ... F name
207 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... F
151 } 208 }
152 // make ready for next iteration 209
153 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k 210 STACK_CHECK(L_, 0);
154 // remove name from fqn stack 211 return r_;
155 _popNameFromFQN(_nextDepth); 212 };
156 STACK_CHECK(L_, 1); 213
157 } // L_: o "r" {c} {fqn} ... {?} 214 STACK_GROW(L_, 2);
158 STACK_CHECK(L_, 0); 215 STACK_CHECK_START_REL(L_, 0);
159 // remove the visited table from the cache, in case a shorter path to the searched object exists 216 // stack top contains the location to search in (table, function, userdata)
160 lua_pushvalue(L_, kIdxTop); // L_: o "r" {c} {fqn} ... {?} {?} 217 [[maybe_unused]] auto const _typeWhere{ luaG_type(L_, kIdxTop) };
161 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} {?} nil 218 LUA_ASSERT(L_, _typeWhere == LuaType::TABLE || _typeWhere == LuaType::USERDATA || _typeWhere == LuaType::FUNCTION);
162 lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... {?} 219 lua_pushvalue(L_, kIdxTop); // L_: o "r" {c} {fqn} ... <> <>
220 lua_rawget(L_, kCache); // L_: o "r" {c} {fqn} ... <> nil/N
221 auto const _visitDepth{ lua_isnil(L_, kIdxTop) ? std::numeric_limits<FqnLength::type>::max() : lua_tointeger(L_, kIdxTop) };
222 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... <>
223 // if location is already visited with a name of <= length, we are done
224 if (_visitDepth <= _fqnLength) {
225 return shortest_;
226 }
227
228 // examined location is not in the cache, add it now
229 // cache[o] = _fqnLength
230 lua_pushvalue(L_, kIdxTop); // L_: o "r" {c} {fqn} ... <> <>
231 lua_pushinteger(L_, _fqnLength); // L_: o "r" {c} {fqn} ... <> <> N
232 lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... <>
233
234 FqnLength r_;
235 // scan location contents
236 switch (luaG_type(L_, kIdxTop)) { // L_: o "r" {c} {fqn} ... <>
237 default:
238 raise_luaL_error(L_, "unexpected error, please investigate");
239 break;
240 case LuaType::TABLE:
241 r_ = _scanTable(L_, shortest_);
242 break;
243 case LuaType::USERDATA:
244 r_ = _scanUserData(L_, shortest_);
245 break;
246 case LuaType::FUNCTION:
247 r_ = _scanFunction(L_, shortest_);
248 break;
249 }
250
163 STACK_CHECK(L_, 0); 251 STACK_CHECK(L_, 0);
164 return shortest_; 252 return r_;
165} 253}
166 254
167// ################################################################################################# 255// #################################################################################################
@@ -170,16 +258,17 @@ static int DiscoverObjectNameRecur(lua_State* const L_, int shortest_, TableInde
170LUAG_FUNC(nameof) 258LUAG_FUNC(nameof)
171{ 259{
172 auto const _argCount{ lua_gettop(L_) }; 260 auto const _argCount{ lua_gettop(L_) };
173 if (_argCount > 1) { 261 if (_argCount != 1) {
174 raise_luaL_argerror(L_, StackIndex{ _argCount }, "too many arguments."); 262 raise_luaL_argerror(L_, StackIndex{ _argCount }, "exactly 1 argument expected");
175 } 263 }
176 264
177 // nil, boolean, light userdata, number and string aren't identifiable 265 // nil, boolean, light userdata, number and string aren't identifiable
178 auto const _isIdentifiable = [L_]() { 266 static constexpr auto _isIdentifiable = [](lua_State* const L_) {
179 auto const _valType{ luaG_type(L_, kIdxTop) }; 267 auto const _valType{ luaG_type(L_, kIdxTop) };
180 return _valType == LuaType::TABLE || _valType == LuaType::FUNCTION || _valType == LuaType::USERDATA || _valType == LuaType::THREAD; 268 return _valType == LuaType::TABLE || _valType == LuaType::FUNCTION || _valType == LuaType::USERDATA || _valType == LuaType::THREAD;
181 }; 269 };
182 if (!_isIdentifiable()) { 270
271 if (!_isIdentifiable(L_)) {
183 luaG_pushstring(L_, luaG_typename(L_, kIdxTop)); // L_: o "type" 272 luaG_pushstring(L_, luaG_typename(L_, kIdxTop)); // L_: o "type"
184 lua_insert(L_, -2); // L_: "type" o 273 lua_insert(L_, -2); // L_: "type" o
185 return 2; 274 return 2;
@@ -198,13 +287,14 @@ LUAG_FUNC(nameof)
198 lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn} 287 lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn}
199 // this is where we start the search 288 // this is where we start the search
200 luaG_pushglobaltable(L_); // L_: o nil {c} {fqn} _G 289 luaG_pushglobaltable(L_); // L_: o nil {c} {fqn} _G
201 std::ignore = DiscoverObjectNameRecur(L_, std::numeric_limits<int>::max(), TableIndex{ 1 }); 290 auto const _foundInG{ DiscoverObjectNameRecur(L_, FqnLength{ std::numeric_limits<FqnLength::type>::max() }) };
202 if (lua_isnil(L_, 2)) { // try again with registry, just in case... 291 if (lua_isnil(L_, 2)) { // try again with registry, just in case...
292 LUA_ASSERT(L_, _foundInG == std::numeric_limits<FqnLength::type>::max());
203 lua_pop(L_, 1); // L_: o nil {c} {fqn} 293 lua_pop(L_, 1); // L_: o nil {c} {fqn}
204 luaG_pushstring(L_, "_R"); // L_: o nil {c} {fqn} "_R" 294 luaG_pushstring(L_, "_R"); // L_: o nil {c} {fqn} "_R"
205 lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn} 295 lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn}
206 lua_pushvalue(L_, kIdxRegistry); // L_: o nil {c} {fqn} _R 296 lua_pushvalue(L_, kIdxRegistry); // L_: o nil {c} {fqn} _R
207 std::ignore = DiscoverObjectNameRecur(L_, std::numeric_limits<int>::max(), TableIndex{ 1 }); 297 [[maybe_unused]] auto const _foundInR{ DiscoverObjectNameRecur(L_, FqnLength{ std::numeric_limits<FqnLength::type>::max() }) };
208 } 298 }
209 lua_pop(L_, 3); // L_: o "result" 299 lua_pop(L_, 3); // L_: o "result"
210 STACK_CHECK(L_, 1); 300 STACK_CHECK(L_, 1);
diff --git a/src/tools.cpp b/src/tools.cpp
index ee6d720..cbfefb0 100644
--- a/src/tools.cpp
+++ b/src/tools.cpp
@@ -95,19 +95,20 @@ namespace tools {
95 95
96 // inspired from tconcat() in ltablib.c 96 // inspired from tconcat() in ltablib.c
97 [[nodiscard]] 97 [[nodiscard]]
98 std::string_view PushFQN(lua_State* const L_, StackIndex const t_, TableIndex const last_) 98 std::string_view PushFQN(lua_State* const L_, StackIndex const t_)
99 { 99 {
100 STACK_CHECK_START_REL(L_, 0); 100 STACK_CHECK_START_REL(L_, 0);
101 // Lua 5.4 pushes &b as light userdata on the stack. be aware of it... 101 // Lua 5.4 pushes &b as light userdata on the stack. be aware of it...
102 luaL_Buffer _b; 102 luaL_Buffer _b;
103 luaL_buffinit(L_, &_b); // L_: ... {} ... &b? 103 luaL_buffinit(L_, &_b); // L_: ... {} ... &b?
104 TableIndex _i{ 1 }; 104 TableIndex _i{ 1 };
105 for (; _i < last_; ++_i) { 105 TableIndex const _last{ static_cast<TableIndex::type>(lua_rawlen(L_, t_)) };
106 for (; _i < _last; ++_i) {
106 lua_rawgeti(L_, t_, _i); 107 lua_rawgeti(L_, t_, _i);
107 luaL_addvalue(&_b); 108 luaL_addvalue(&_b);
108 luaL_addlstring(&_b, "/", 1); 109 luaL_addlstring(&_b, "/", 1);
109 } 110 }
110 if (_i == last_) { // add last value (if interval was not empty) 111 if (_i == _last) { // add last value (if interval was not empty)
111 lua_rawgeti(L_, t_, _i); 112 lua_rawgeti(L_, t_, _i);
112 luaL_addvalue(&_b); 113 luaL_addvalue(&_b);
113 } 114 }
@@ -151,7 +152,7 @@ static void update_lookup_entry(lua_State* const L_, StackIndex const ctxBase_,
151 TableIndex const _deeper{ depth_ + 1 }; 152 TableIndex const _deeper{ depth_ + 1 };
152 lua_rawseti(L_, _fqn, _deeper); // L_: ... {bfc} k o name? 153 lua_rawseti(L_, _fqn, _deeper); // L_: ... {bfc} k o name?
153 // generate name 154 // generate name
154 std::string_view const _newName{ tools::PushFQN(L_, _fqn, _deeper) }; // L_: ... {bfc} k o name? "f.q.n" 155 std::string_view const _newName{ tools::PushFQN(L_, _fqn) }; // L_: ... {bfc} k o name? "f.q.n"
155 // Lua 5.2 introduced a hash randomizer seed which causes table iteration to yield a different key order 156 // Lua 5.2 introduced a hash randomizer seed which causes table iteration to yield a different key order
156 // on different VMs even when the tables are populated the exact same way. 157 // on different VMs even when the tables are populated the exact same way.
157 // Also, when Lua is built with compatibility options (such as LUA_COMPAT_ALL), some base libraries register functions under multiple names. 158 // Also, when Lua is built with compatibility options (such as LUA_COMPAT_ALL), some base libraries register functions under multiple names.
diff --git a/src/tools.hpp b/src/tools.hpp
index 7955263..420b5f8 100644
--- a/src/tools.hpp
+++ b/src/tools.hpp
@@ -36,6 +36,6 @@ static constexpr RegistryUniqueKey kLookupRegKey{ 0xBF1FC5CF3C6DD47Bull }; // re
36namespace tools { 36namespace tools {
37 void PopulateFuncLookupTable(lua_State* L_, StackIndex i_, std::string_view const& name_); 37 void PopulateFuncLookupTable(lua_State* L_, StackIndex i_, std::string_view const& name_);
38 [[nodiscard]] 38 [[nodiscard]]
39 std::string_view PushFQN(lua_State* L_, StackIndex t_, TableIndex last_); 39 std::string_view PushFQN(lua_State* L_, StackIndex t_);
40 void SerializeRequire(lua_State* L_); 40 void SerializeRequire(lua_State* L_);
41} // namespace tools 41} // namespace tools
diff --git a/unit_tests/lane_tests.cpp b/unit_tests/lane_tests.cpp
index 77c7f61..63626ae 100644
--- a/unit_tests/lane_tests.cpp
+++ b/unit_tests/lane_tests.cpp
@@ -8,16 +8,25 @@ TEST_CASE("lanes.nameof")
8 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; 8 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
9 S.requireSuccess("lanes = require 'lanes'.configure()"); 9 S.requireSuccess("lanes = require 'lanes'.configure()");
10 10
11 // no argument is not good
12 S.requireFailure("local t, n = lanes.nameof()");
13
14 // more than one argument is not good
15 S.requireFailure("local t, n = lanes.nameof(true, false)");
16
11 // a constant is itself, stringified 17 // a constant is itself, stringified
12 S.requireReturnedString("local t, n = lanes.nameof('bob'); return t .. ': ' .. tostring(n)", "string: bob"); 18 S.requireReturnedString("local t, n = lanes.nameof('bob'); return t .. ': ' .. tostring(n)", "string: bob");
13 S.requireReturnedString("local t, n = lanes.nameof(true); return t .. ': ' .. tostring(n)", "boolean: true"); 19 S.requireReturnedString("local t, n = lanes.nameof(true); return t .. ': ' .. tostring(n)", "boolean: true");
14 S.requireReturnedString("local t, n = lanes.nameof(42); return t .. ': ' .. tostring(n)", "number: 42"); 20 S.requireReturnedString("local t, n = lanes.nameof(42); return t .. ': ' .. tostring(n)", "number: 42");
21
22 // a temporary object has no name
15 S.requireReturnedString("local t, n = lanes.nameof({}); return t .. ': ' .. tostring(n)", "table: nil"); 23 S.requireReturnedString("local t, n = lanes.nameof({}); return t .. ': ' .. tostring(n)", "table: nil");
24 S.requireReturnedString("local t, n = lanes.nameof(function() end); return t .. ': ' .. tostring(n)", "function: nil");
16 25
17 // look for something in _G 26 // look for something in _G
18 S.requireReturnedString("local t, n = lanes.nameof(print); return t .. ': ' .. tostring(n)", "function: _G/print"); 27 S.requireReturnedString("local t, n = lanes.nameof(print); return t .. ': ' .. tostring(n)", "function: _G/print()");
19 S.requireReturnedString("local t, n = lanes.nameof(string); return t .. ': ' .. tostring(n)", "table: _G/string"); 28 S.requireReturnedString("local t, n = lanes.nameof(string); return t .. ': ' .. tostring(n)", "table: _G/string[]");
20 S.requireReturnedString("local t, n = lanes.nameof(string.sub); return t .. ': ' .. tostring(n)", "function: _G/string/sub"); 29 S.requireReturnedString("local t, n = lanes.nameof(string.sub); return t .. ': ' .. tostring(n)", "function: _G/string[]/sub()");
21} 30}
22 31
23// ################################################################################################# 32// #################################################################################################